-
Notifications
You must be signed in to change notification settings - Fork 273
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
PHP: Make proc_open work without requiring an explicit sleep() #951
Comments
I investigated a bit more and added <?php
// proc_open `less` and pipe some dummy input to it
$descriptorspec = array(
0 => array("pipe", "r"), // stdin
1 => array("pipe", "w"), // stdout
2 => array("pipe", "w") // stderr
);
$process = proc_open('less', $descriptorspec, $pipes);
if (is_resource($process)) {
echo "Pipe 0";
fwrite($pipes[0], "Hello world!\n");
fclose($pipes[0]);
echo "Pipe 1";
echo fread($pipes[1], 1024);
fclose($pipes[1]);
// echo "Pipe 2";
// echo stream_get_contents($pipes[2]);
// fclose($pipes[2]);
echo "close";
proc_close($process);
} What I found is that patching the syscalls library with Asyncify calls wouldn't help at all. It seems like
At the same time, Node.js writable docs says that it...
I checked, and that
Perhaps a better choice would be yielding after each |
Shims read(2) functionallity by providing an alternative read() function called wasm_read() that gracefully handles blocking pipes. This is neecessary because Emscripten does not support blocking pipes and instead returns EWOULDBLOCK. See: * #951 * emscripten-core/emscripten#13214 ## Testing instructions Confirm the `proc_open()` tests pass on CI. This PR adjusts a few of them to make sure the output is read without the tricky sleep() call.
Shims read(2) functionallity by providing an alternative read() function called wasm_read() that gracefully handles blocking pipes. This is neecessary because Emscripten returns EWOULDBLOCK when reading from a blocking pipe, whereas a clang-compiled program would wait until data becomes available. See: * #951 * emscripten-core/emscripten#13214 ## Other changes This PR also ships: * A regexp fix for `@php-wasm/cli` to preserve a trailing whitespace when rewrite PHP-related spawn shell commands. * A fix to correctly propagate the child process exit code back to PHP. The WordPress bootstrap script for PHPUnit needs that to work correctly. ## Motivation ### wp-cli Without this PR, running `wp-cli.phar` doesn't output anything. The output is piped through a pager like `less` using `proc_open` and then displayed by reading from a blocking pipe. With this PR, running `wp-cli.phar` returns its regular help message as the pager pipe can be read: ``` NAME wp DESCRIPTION Manage WordPress through the command-line. SYNOPSIS wp <command> SUBCOMMANDS cache Adds, removes, fetches, and flushes the WP Object Cache object. cap Adds, removes, and lists capabilities of a user role. cli Reviews current WP-CLI info, checks for updates, or views defined aliases. ... ``` ### PHPUnit With this PR, Playground can run PHPunit on `wordpress-develop`! ``` TMPDIR=/tmp PHP=8.2 node --loader ../plugins/playground/packages/nx-extensions/src/executors/built-script/loader.mjs ../plugins/playground/dist/packages/php-wasm/cli/main.js ./vendor/bin/phpunit --no-coverage -v (node:87723) ExperimentalWarning: Custom ESM Loaders is an experimental feature. This feature could change at any time (Use `node --trace-warnings ...` to show where the warning was created) Installing... aaabb int(0) Running as single site... To run multisite, use -c tests/phpunit/multisite.xml Not running ajax tests. To execute these, use --group ajax. Not running ms-files tests. To execute these, use --group ms-files. Not running external-http tests. To execute these, use --group external-http. PHPUnit 9.6.15 by Sebastian Bergmann and contributors. Runtime: PHP 8.2.10-dev Configuration: /Users/cloudnik/www/Automattic/core/wordpress-develop/phpunit.xml.dist Warning: Your XML configuration validates against a deprecated schema. Suggestion: Migrate your XML configuration using "--migrate-configuration"! ........................................................... 59 / 17622 ( 0%) ........................................................... 118 / 17622 ( 0%) ............................FFFFFF......................... 177 / 17622 ( 1%) ........................................................... 236 / 17622 ( 1%) ``` ## Testing instructions Confirm the `proc_open()` tests pass on CI. This PR adjusts a few of them to make sure the output is read without the tricky sleep() call. Related: * #827 * #930 CC @mho22
Fixed in #931 |
@adamziel I am actually trying
Here are the results when I use
First of all, congratulations, there is no more need for sleep(1) in this case! What I currently see, is the
[ converting a wav file in an mp3 file ] php does this correctly while php-wasm does not really execute the command. Any idea where I could find a beginning of a solution to improve this ? Maybe should I create another issue ? |
@adamziel Yes you're right. I was so into my PR that I forgot I was using the array style to try the command. Your comment made me doubt and it works in a string. Sorry about that... Thank you for your quick answers and don't hesitate to give me some tips when you have time next week about the dynamic pointer generation as mentionned in the last comment of my #944 patch. But I hope the idea I have will fix the PR this week-end! |
Problem
Playground supports
proc_open()
through a custom function calledjs_open_process()
added in #596. Instead of deferring to OS process opening functions, like PHP does by default, it calls a user-defined callback that spawns a new process and returns aChildProcess
object.js_open_process
then writes any stdin data usingcp.stdin.write(dataStr);
and captures the output usingcp.stdout.on('data', function (data) {
. Thestdout
event listener is asynchronous and may receive data after 1ms or 100ms – we don't really know.What does native PHP do?
Let's consider the following script:
It will output "Hello world!\n". However, there are some nuances.
stream_get_contents
expects the input pipe to close. If we commentfclose($pipes[0]);
in that script above, it will hand indefinitely. I'm not sure where that behavior comes from, but both_php_stream_fill_read_buffer
andphp_stream_eof
sound like interesting candidates.fread()
reads whatever is available and returns. If we replacestream_get_contents()
withfread($pipes[1], 1024);
and comment thefclose()
call, the script will finish and outputHello world!\n
If I adjust the
proc_open()
call to beproc_open('sleep 1; less')
, then thefread()
call takes more than 1 second which tells me something in it is blocking after all.Similarly:
Which makes it seem like
fread
waits for any output, regardless of the buffer size. In this case, we could yield back in_php_stream_read()
and until the process fd has some data available.If, however, I call
stream_set_blocking($pipes[1], 0);
, then thefread()
call returns instantly. For plain streams, that call is translated toflags |= O_NONBLOCK; fcntl(fd, F_SETFL, flags)
. Cool! We're getting somewhere!Here's some more resources:
https://bugs.php.net/bug.php?id=47918
Possible Solutions
A proper fix would yield back to the event loop in the same place where PHP waits for data.
Idea 1 – Async-compatible libc implementation
Playground currently patches PHP with custom implementations of functions like
select(2)
in an attempt to make the synchronous C code work in an asynchronous JavaScript runtime. These patches target PHP whereas in reality their goal is to replace blocking syscalls with async-compatible syscalls. Instead of PHP, we should be patching the syscalls library.To illustrate the issue, here's the
wasm_select
implementation:If the relevant syscalls knew how to wait for asynchronous events, we wouldn't need that at all.
Here's the Emscripten-provided
library_syscall.js
file that handles syscalls:https://github.com/emscripten-core/emscripten/blob/29b0eaacfda55c5c034453a880609cca91f39d8e/src/library_syscall.js
Functions like
__syscall__newselect
or__syscall_poll
could returnAsyncify.sleep()
whenever required, handle the asynchronous data flow, and callwakeUp()
whenever a regular OS would.Idea 2 – Handle
EWOULDBLOCK
in_fd_read
I patched
_fd_read
as follows:And got an error with
errno=6
. It seems like in Emscripten that meansEWOULDBLOCK
, which makes sense. I found this related Emscripten issue:pipe() doesn't create a blocking pipe
It seems like we could detect that error and return
Asyncify.sleep()
instead.The text was updated successfully, but these errors were encountered: