-
Notifications
You must be signed in to change notification settings - Fork 360
PHP: stream stdout and stderr via StreamedPHPResponse #2266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Implements a php.runStream() method that returns a StreamedPHPResponse instance. It exposes stdout and stderr as ReadableStreams, allowing the caller to interact with partial output data. Before this PR, we only had php.run() that buffered stdout and stderr data and returned it all at once after the PHP code was fully executed. ## Implementation We register three FS devices at: * /internal/stdout * /internal/stderr * /internal/headers They are private to every PHP instance and are never shared with other runtimes. Then, in JavaScript, whenever a chunk of data is written to either of these devices, we propagate it to consumer via a callback, e.g. `PHPWASM.onStdout(chunk)`. Users of the `PHP` class never have to interact with these devices or callbacks directly. The PHP class creates the relevant ReadableStreams and pushes the data through them – see php.#executeWithErrorHandling() for details. #### Why not use Emscripten's stdout and stderr? Emscripten's native stdout and stderr devices stop processing data when they encounter the first null byte. However, null bytes are common when dealing with binary data. ### Backwards Compatibility php.run() continues to work. It creates a streamed response under the hood and buffers the streamed output before returning a buffered `PHPResponse` object. ## Remaining work * Add streaming-specific tests ## Follow-up work * Stream the response bytes in the web/service worker Remove old PHP CLI bindings
@github-project-automation
github-project-automation
bot
added this to Playground Board
Jun 12, 2025
@github-project-automation
github-project-automation
bot
moved this to Inbox
in Playground Board
Jun 12, 2025
@adamziel
adamziel
force-pushed
the
push-lzqtsuspwwoy
branch
7 times, most recently
from
June 13, 2025 08:48
18983ae to
b547441
Compare
@adamziel
adamziel
added
[Type] Enhancement
New feature or request
[Feature] PHP.wasm
[Package][@php-wasm] Web
[Package][@php-wasm] Node
labels
Jun 13, 2025
@adamziel
adamziel
force-pushed
the
push-lzqtsuspwwoy
branch
8 times, most recently
from
June 15, 2025 22:25
745ea86 to
085a855
Compare
@adamziel
adamziel
force-pushed
the
push-lzqtsuspwwoy
branch
from
June 15, 2025 22:39
085a855 to
8b10520
Compare
@adamziel
adamziel
changed the title
(削除) PHP: Stream stdout and stderr (削除ここまで)
(追記) PHP: StreamedPHPResponse that streams stdout and stderr (追記ここまで)
Jun 16, 2025
@adamziel
adamziel
changed the title
(削除) PHP: StreamedPHPResponse that streams stdout and stderr (削除ここまで)
(追記) PHP: stream stdout and stderr via StreamedPHPResponse (追記ここまで)
Jun 16, 2025
Cool work, @adamziel!
adamziel
added a commit
that referenced
this pull request
Aug 4, 2025
Guards against releasing the "request in progress" semaphore too early in the php.runStream() call. A single PHP runtime can only handle one request at a time. The PHP class calls a `wasm_sapi_handle_request` C function that initializes the PHP runtime and starts the request. That function is asynchronous and may yield back to the event loop before the request is fully handled, the exit code known, and the runtime is cleaned up and prepared for another request. The PHP class uses an async semaphore to protect against calling `wasm_sapi_handle_request` again while a previous call is still running. However, PR 2266 [1] introduced a regression where the semaphore was released too early. As a result, it opened the runtime to a race condition where a subsequent runStream() call tried to run PHP code on a runtime that was in a middle of handling a request. This test ensures that two runStream() calls can be made without crashing the runtime. [1] #2266
adamziel
added a commit
that referenced
this pull request
Aug 4, 2025
Guards against releasing the "request in progress" semaphore too early in the php.runStream() call. A single PHP runtime can only handle one request at a time. The PHP class calls a `wasm_sapi_handle_request` C function that initializes the PHP runtime and starts the request. That function is asynchronous and may yield back to the event loop before the request is fully handled, the exit code known, and the runtime is cleaned up and prepared for another request. The PHP class uses an async semaphore to protect against calling `wasm_sapi_handle_request` again while a previous call is still running. However, PR 2266 [1] introduced a regression where the semaphore was released too early. As a result, it opened the runtime to a race condition where a subsequent runStream() call tried to run PHP code on a runtime that was in a middle of handling a request. This test ensures that two runStream() calls can be made without crashing the runtime. [1] #2266
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
Implements a
php.runStream()method that returns aStreamedPHPResponseinstance:It exposes
stdoutandstderrasReadableStream-s, allowing the caller to interactwith partial output data.
Before this PR, we only had
php.run()that bufferedstdoutandstderrdata and returned it all at once after the PHP code was fullyexecuted.
Usage example
API changes
php.runStream(request: PHPRequest): StreamedPHPResponsephp.cli()from integer exit code toStreamedPHPResponseImplementation
php.js registers three FS devices at:
They are private to every PHP instance and are never shared with other runtimes.
Then, in JavaScript, whenever a chunk of data is written to either of these devices, we propagate
it to consumer via a callback, e.g.
PHPWASM.onStdout(chunk).Consumers of the
PHPclass never have to interact with these devices or callbacks directly.The PHP class creates the relevant ReadableStreams and pushes the data through them
– see php.#executeWithErrorHandling() for details.
Why not use Emscripten's stdout and stderr?
Emscripten's native stdout and stderr devices stop processing data when they encounter
the first null byte. However, null bytes are common when dealing with binary data.
Backwards Compatibility
php.cli()now returns aStreamedPHPResponseinstance and not an integer exit code.php.run()continues to work as before. Internally, it now creates a streamed response and buffers the output before returning aPHPResponseobject.Follow-up work