From e7e380e2aaa00493cc144235616ce61c90329d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 9 Jul 2021 09:07:05 +0200 Subject: [PATCH 1/2] Simplify usage by supporting new default loop --- .github/workflows/ci.yml | 3 ++ README.md | 58 +++++++++++++--------------- composer.json | 23 +++++++++-- examples/01-stdio.php | 14 +++---- examples/02-race.php | 9 +---- examples/03-stdout-stderr.php | 7 +--- examples/04-terminate.php | 10 ++--- examples/05-stdio-sockets.php | 7 +--- examples/11-benchmark-read.php | 9 +---- examples/12-benchmark-write.php | 9 +---- examples/13-benchmark-throughput.php | 9 +---- examples/21-fds.php | 7 +--- examples/22-race-exit.php | 9 +---- examples/23-forward-socket.php | 9 +---- src/Process.php | 16 ++++++-- tests/AbstractProcessTest.php | 15 +++++++ 16 files changed, 99 insertions(+), 115 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2b0b287..eb7f810 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,8 @@ jobs: with: php-version: ${{ matrix.php }} coverage: xdebug + env: + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: composer install - run: vendor/bin/phpunit --coverage-text if: ${{ matrix.php >= 7.3 }} @@ -41,6 +43,7 @@ jobs: name: PHPUnit (HHVM) runs-on: ubuntu-18.04 continue-on-error: true + if: false # temporarily skipped until https://github.com/azjezz/setup-hhvm/issues/3 is addressed steps: - uses: actions/checkout@v2 - uses: azjezz/setup-hhvm@v1 diff --git a/README.md b/README.md index fee6800..50b740c 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,8 @@ as [Streams](https://github.com/reactphp/stream). ## Quickstart example ```php -$loop = React\EventLoop\Factory::create(); - $process = new React\ChildProcess\Process('echo foo'); -$process->start($loop); +$process->start(); $process->stdout->on('data', function ($chunk) { echo $chunk; @@ -41,8 +39,6 @@ $process->stdout->on('data', function ($chunk) { $process->on('exit', function($exitCode, $termSignal) { echo 'Process exited with code ' . $exitCode . PHP_EOL; }); - -$loop->run(); ``` See also the [examples](examples). @@ -80,7 +76,7 @@ you can use any of their events and methods as usual: ```php $process = new Process($command); -$process->start($loop); +$process->start(); $process->stdout->on('data', function ($chunk) { echo $chunk; @@ -113,7 +109,7 @@ The `Process` class allows you to pass any kind of command line string: ```php $process = new Process('echo test'); -$process->start($loop); +$process->start(); ``` The command line string usually consists of a whitespace-separated list with @@ -129,7 +125,7 @@ $bin = 'C:\\Program files (x86)\\PHP\\php.exe'; $file = 'C:\\Users\\me\\Desktop\\Application\\main.php'; $process = new Process(escapeshellarg($bin) . ' ' . escapeshellarg($file)); -$process->start($loop); +$process->start(); ``` By default, PHP will launch processes by wrapping the given command line string @@ -146,7 +142,7 @@ streams from the wrapping shell command like this: ```php $process = new Process('echo run && demo || echo failed'); -$process->start($loop); +$process->start(); ``` > Note that [Windows support](#windows-compatibility) is limited in that it @@ -168,7 +164,7 @@ boundary between each sub-command like this: ```php $process = new Process('cat first && echo --- && cat second'); -$process->start($loop); +$process->start(); ``` As an alternative, considering launching one process at a time and listening on @@ -177,11 +173,11 @@ This will give you an opportunity to configure the subsequent process I/O stream ```php $first = new Process('cat first'); -$first->start($loop); +$first->start(); -$first->on('exit', function () use ($loop) { +$first->on('exit', function () { $second = new Process('cat second'); - $second->start($loop); + $second->start(); }); ``` @@ -191,7 +187,7 @@ also applies to running the most simple single command: ```php $process = new Process('yes'); -$process->start($loop); +$process->start(); ``` This will actually spawn a command hierarchy similar to this on Unix: @@ -212,7 +208,7 @@ wrapping shell process to be replaced by our process: ```php $process = new Process('exec yes'); -$process->start($loop); +$process->start(); ``` This will show a resulting command hierarchy similar to this: @@ -244,7 +240,7 @@ arguments: ```php $process = new Process('sleep 10'); -$process->start($loop); +$process->start(); $process->on('exit', function ($code, $term) { if ($term === null) { @@ -292,9 +288,9 @@ accordingly when terminating a process: ```php $process = new Process('sleep 10'); -$process->start($loop); +$process->start(); -$loop->addTimer(2.0, function () use ($process) { +Loop::addTimer(2.0, function () use ($process) { foreach ($process->pipes as $pipe) { $pipe->close(); } @@ -308,9 +304,9 @@ inherited process pipes as [mentioned above](#command). ```php $process = new Process('exec sleep 10'); -$process->start($loop); +$process->start(); -$loop->addTimer(2.0, function () use ($process) { +Loop::addTimer(2.0, function () use ($process) { $process->terminate(); }); ``` @@ -321,9 +317,9 @@ For example, the following can be used to "soft-close" a `cat` process: ```php $process = new Process('cat'); -$process->start($loop); +$process->start(); -$loop->addTimer(2.0, function () use ($process) { +Loop::addTimer(2.0, function () use ($process) { $process->stdin->end(); }); ``` @@ -366,7 +362,7 @@ $fds = array( ); $process = new Process($cmd, null, null, $fds); -$process->start($loop); +$process->start(); ``` Unless your use case has special requirements that demand otherwise, you're @@ -429,7 +425,7 @@ instead throw a `LogicException` on Windows by default: ```php // throws LogicException on Windows $process = new Process('ping example.com'); -$process->start($loop); +$process->start(); ``` There are a number of alternatives and workarounds as detailed below if you want @@ -449,7 +445,7 @@ to run a child process on Windows, each with its own set of pros and cons: ['socket'] ] ); - $process->start($loop); + $process->start(); $process->stdout->on('data', function ($chunk) { echo $chunk; @@ -471,7 +467,7 @@ to run a child process on Windows, each with its own set of pros and cons: ```php $process = new Process('ping example.com', null, null, array()); - $process->start($loop); + $process->start(); $process->on('exit', function ($exitcode) { echo 'exit with ' . $exitcode . PHP_EOL; @@ -494,7 +490,7 @@ to run a child process on Windows, each with its own set of pros and cons: $stdout = tmpfile(), array('file', 'nul', 'w') )); - $process->start($loop); + $process->start(); $process->on('exit', function ($exitcode) use ($stdout) { echo 'exit with ' . $exitcode . PHP_EOL; @@ -520,7 +516,7 @@ to run a child process on Windows, each with its own set of pros and cons: omit any actual standard I/O pipes like this: ```php - $server = new React\Socket\Server('127.0.0.1:0', $loop); + $server = new React\Socket\Server('127.0.0.1:0'); $server->on('connection', function (React\Socket\ConnectionInterface $connection) { $connection->on('data', function ($chunk) { echo $chunk; @@ -529,7 +525,7 @@ to run a child process on Windows, each with its own set of pros and cons: $command = 'ping example.com | foobar ' . escapeshellarg($server->getAddress()); $process = new Process($command, null, null, array()); - $process->start($loop); + $process->start(); $process->on('exit', function ($exitcode) use ($server) { $server->close(); @@ -560,7 +556,7 @@ to run a child process on Windows, each with its own set of pros and cons: $code = '$s=stream_socket_client($argv[1]);do{fwrite($s,$d=fread(STDIN, 8192));}while(isset($d[0]));'; $command = 'ping example.com | php -r ' . escapeshellarg($code) . ' ' . escapeshellarg($server->getAddress()); $process = new Process($command, null, null, array()); - $process->start($loop); + $process->start(); ``` See also [example #23](examples/23-forward-socket.php). @@ -580,7 +576,7 @@ particular, this means that shell built-in functions such as `echo hello` or ```php $process = new Process('cmd /c echo hello', null, null, $pipes); -$process->start($loop); +$process->start(); ``` ## Install diff --git a/composer.json b/composer.json index bdaa7a8..90d87d8 100644 --- a/composer.json +++ b/composer.json @@ -28,12 +28,13 @@ "require": { "php": ">=5.3.0", "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", - "react/stream": "^1.0 || ^0.7.6" + "react/event-loop": "dev-master#78f7f43 as 1.2.0", + "react/stream": "dev-default-loop#e617d63 as 1.2.0" }, "require-dev": { "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", - "react/socket": "^1.0", + "react/dns": "dev-default-loop#28e5df1 as 1.8.0", + "react/socket": "dev-default-loop#b471dc7 as 1.8.0", "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" }, "autoload": { @@ -41,5 +42,19 @@ }, "autoload-dev": { "psr-4": { "React\\Tests\\ChildProcess\\": "tests" } - } + }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/clue-labs/dns" + }, + { + "type": "vcs", + "url": "https://github.com/clue-labs/socket" + }, + { + "type": "vcs", + "url": "https://github.com/clue-labs/stream" + } + ] } diff --git a/examples/01-stdio.php b/examples/01-stdio.php index e15f00d..79c9f8b 100644 --- a/examples/01-stdio.php +++ b/examples/01-stdio.php @@ -1,7 +1,7 @@ start($loop); +$process->start(); $process->stdout->on('data', function ($chunk) { echo $chunk; @@ -23,14 +21,12 @@ }); // periodically send something to stream -$periodic = $loop->addPeriodicTimer(0.2, function () use ($process) { +$periodic = Loop::addPeriodicTimer(0.2, function () use ($process) { $process->stdin->write('hello'); }); // stop sending after a few seconds -$loop->addTimer(2.0, function () use ($periodic, $loop, $process) { - $loop->cancelTimer($periodic); +Loop::addTimer(2.0, function () use ($periodic, $process) { + Loop::cancelTimer($periodic); $process->stdin->end(); }); - -$loop->run(); diff --git a/examples/02-race.php b/examples/02-race.php index d25c7f5..2e46d62 100644 --- a/examples/02-race.php +++ b/examples/02-race.php @@ -1,6 +1,5 @@ start($loop); +$first->start(); $second = new Process('sleep 1; echo hallo'); -$second->start($loop); +$second->start(); $first->stdout->on('data', function ($chunk) { echo $chunk; @@ -24,5 +21,3 @@ $second->stdout->on('data', function ($chunk) { echo $chunk; }); - -$loop->run(); diff --git a/examples/03-stdout-stderr.php b/examples/03-stdout-stderr.php index db40a86..657ff85 100644 --- a/examples/03-stdout-stderr.php +++ b/examples/03-stdout-stderr.php @@ -1,6 +1,5 @@ &2;sleep 1;echo error;sleep 1;nope'); -$process->start($loop); +$process->start(); $process->stdout->on('data', function ($chunk) { echo '(' . $chunk . ')'; @@ -25,5 +22,3 @@ $process->on('exit', function ($code) { echo 'EXIT with code ' . $code . PHP_EOL; }); - -$loop->run(); diff --git a/examples/04-terminate.php b/examples/04-terminate.php index ecb940d..55a0680 100644 --- a/examples/04-terminate.php +++ b/examples/04-terminate.php @@ -1,15 +1,13 @@ start($loop); +$process->start(); // report when process exits $process->on('exit', function ($exit, $term) { @@ -17,11 +15,9 @@ }); // forcefully terminate process after 2s -$loop->addTimer(2.0, function () use ($process) { +Loop::addTimer(2.0, function () use ($process) { foreach ($process->pipes as $pipe) { $pipe->close(); } $process->terminate(); }); - -$loop->run(); diff --git a/examples/05-stdio-sockets.php b/examples/05-stdio-sockets.php index 291c00c..c3e743f 100644 --- a/examples/05-stdio-sockets.php +++ b/examples/05-stdio-sockets.php @@ -1,6 +1,5 @@ start($loop); +$process->start(); $process->stdout->on('data', function ($chunk) { echo '(' . $chunk . ')'; @@ -34,5 +31,3 @@ $process->on('exit', function ($code) { echo 'EXIT with code ' . $code . PHP_EOL; }); - -$loop->run(); diff --git a/examples/11-benchmark-read.php b/examples/11-benchmark-read.php index 5318d26..08a57b4 100644 --- a/examples/11-benchmark-read.php +++ b/examples/11-benchmark-read.php @@ -4,7 +4,6 @@ // $ php examples/11-benchmark.php echo test // $ php examples/11-benchmark.php dd if=/dev/zero bs=1M count=1000 -use React\EventLoop\Factory; use React\ChildProcess\Process; require __DIR__ . '/../vendor/autoload.php'; @@ -15,9 +14,7 @@ $cmd = isset($argv[1]) ? implode(' ', array_slice($argv, 1)) : 'dd if=/dev/zero bs=1M count=1000'; -$loop = Factory::create(); - -$info = new React\Stream\WritableResourceStream(STDERR, $loop); +$info = new React\Stream\WritableResourceStream(STDERR); $info->write('Counts number of chunks/bytes received from process STDOUT' . PHP_EOL); $info->write('Command: ' . $cmd . PHP_EOL); if (extension_loaded('xdebug')) { @@ -25,7 +22,7 @@ } $process = new Process($cmd); -$process->start($loop); +$process->start(); $start = microtime(true); $chunks = 0; @@ -47,5 +44,3 @@ $process->stdout->on('error', array($info, 'write')); $process->stderr->on('data', 'printf'); $process->stdout->on('error', 'printf'); - -$loop->run(); diff --git a/examples/12-benchmark-write.php b/examples/12-benchmark-write.php index bac247f..fae5b94 100644 --- a/examples/12-benchmark-write.php +++ b/examples/12-benchmark-write.php @@ -1,6 +1,5 @@ write('Pipes data to process STDIN' . PHP_EOL); if (extension_loaded('xdebug')) { $info->write('NOTICE: The "xdebug" extension is loaded, this has a major impact on performance.' . PHP_EOL); } $process = new Process('dd of=/dev/zero'); -$process->start($loop); +$process->start(); // 10000 * 100 KB => 1 GB $i = 10000; @@ -43,5 +40,3 @@ $process->stdout->on('error', 'printf'); $process->stderr->on('data', 'printf'); $process->stdout->on('error', 'printf'); - -$loop->run(); diff --git a/examples/13-benchmark-throughput.php b/examples/13-benchmark-throughput.php index 743f4b4..e0da6c6 100644 --- a/examples/13-benchmark-throughput.php +++ b/examples/13-benchmark-throughput.php @@ -1,6 +1,5 @@ write('Pipes data through process STDIN and reads STDOUT again' . PHP_EOL); if (extension_loaded('xdebug')) { $info->write('NOTICE: The "xdebug" extension is loaded, this has a major impact on performance.' . PHP_EOL); } $process = new Process('cat'); -$process->start($loop); +$process->start(); $start = microtime(true); $chunks = 0; @@ -58,5 +55,3 @@ $process->stdout->on('error', 'printf'); $process->stderr->on('data', 'printf'); $process->stdout->on('error', 'printf'); - -$loop->run(); diff --git a/examples/21-fds.php b/examples/21-fds.php index e495d6a..c1e341d 100644 --- a/examples/21-fds.php +++ b/examples/21-fds.php @@ -1,6 +1,5 @@ &- 2>&-;exec ls -la /proc/self/fd', null, null, array( 1 => array('pipe', 'w') )); -$process->start($loop); +$process->start(); $process->stdout->on('data', function ($chunk) { echo $chunk; @@ -23,5 +20,3 @@ $process->on('exit', function ($code) { echo 'EXIT with code ' . $code . PHP_EOL; }); - -$loop->run(); diff --git a/examples/22-race-exit.php b/examples/22-race-exit.php index 1ad9259..95876dd 100644 --- a/examples/22-race-exit.php +++ b/examples/22-race-exit.php @@ -1,24 +1,19 @@ start($loop); +$first->start(); $first->on('exit', function ($code) { echo 'First closed ' . $code . PHP_EOL; }); $second = new Process('php -r "sleep(1);"', null, null, array()); -$second->start($loop); +$second->start(); $second->on('exit', function ($code) { echo 'Second closed ' . $code . PHP_EOL; }); - -$loop->run(); diff --git a/examples/23-forward-socket.php b/examples/23-forward-socket.php index 7d37ddd..b0a9da7 100644 --- a/examples/23-forward-socket.php +++ b/examples/23-forward-socket.php @@ -2,14 +2,11 @@ // see also 05-stdio-sockets.php -use React\EventLoop\Factory; use React\ChildProcess\Process; require __DIR__ . '/../vendor/autoload.php'; -$loop = Factory::create(); - -$server = new React\Socket\Server('127.0.0.1:0', $loop); +$server = new React\Socket\Server('127.0.0.1:0'); $server->on('connection', function (React\Socket\ConnectionInterface $connection) { $connection->on('data', function ($chunk) { // escape control codes (useful in case encoding or binary data is not working as expected) @@ -34,11 +31,9 @@ null, array() ); -$process->start($loop); +$process->start(); $process->on('exit', function ($code) use ($server) { $server->close(); echo PHP_EOL . 'Process closed ' . $code . PHP_EOL; }); - -$loop->run(); diff --git a/src/Process.php b/src/Process.php index bac1b9b..9e876b4 100644 --- a/src/Process.php +++ b/src/Process.php @@ -3,6 +3,7 @@ namespace React\ChildProcess; use Evenement\EventEmitter; +use React\EventLoop\Loop; use React\EventLoop\LoopInterface; use React\Stream\ReadableResourceStream; use React\Stream\ReadableStreamInterface; @@ -27,7 +28,7 @@ * * ```php * $process = new Process('sleep 10'); - * $process->start($loop); + * $process->start(); * * $process->on('exit', function ($code, $term) { * if ($term === null) { @@ -153,16 +154,23 @@ public function __construct($cmd, $cwd = null, array $env = null, array $fds = n * After the process is started, the standard I/O streams will be constructed * and available via public properties. * - * @param LoopInterface $loop Loop interface for stream construction - * @param float $interval Interval to periodically monitor process state (seconds) + * This method takes an optional `LoopInterface|null $loop` parameter that can be used to + * pass the event loop instance to use for this process. You can use a `null` value + * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). + * This value SHOULD NOT be given unless you're sure you want to explicitly use a + * given event loop instance. + * + * @param ?LoopInterface $loop Loop interface for stream construction + * @param float $interval Interval to periodically monitor process state (seconds) * @throws \RuntimeException If the process is already running or fails to start */ - public function start(LoopInterface $loop, $interval = 0.1) + public function start(LoopInterface $loop = null, $interval = 0.1) { if ($this->isRunning()) { throw new \RuntimeException('Process is already running'); } + $loop = $loop ?: Loop::get(); $cmd = $this->cmd; $fdSpec = $this->fds; $sigchild = null; diff --git a/tests/AbstractProcessTest.php b/tests/AbstractProcessTest.php index f19df97..f0f39a2 100644 --- a/tests/AbstractProcessTest.php +++ b/tests/AbstractProcessTest.php @@ -49,6 +49,21 @@ public function testStartWillAssignPipes() $this->assertSame($process->stderr, $process->pipes[2]); } + /** + * @depends testPipesWillBeUnsetBeforeStarting + */ + public function testStartWithoutLoopAssignsLoopAutomatically() + { + $process = new Process('echo foo'); + $process->start(); + + $ref = new \ReflectionProperty($process->stdin, 'loop'); + $ref->setAccessible(true); + $loop = $ref->getValue($process->stdin); + + $this->assertInstanceOf('React\EventLoop\LoopInterface', $loop); + } + /** * @depends testStartWillAssignPipes * @requires PHP 8 From efac6d489b563406d9f1cd5dede31faf3f796db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 9 Jul 2021 09:24:18 +0200 Subject: [PATCH 2/2] Update to stable reactphp/event-loop v1.2.0 & updated stream and socket --- .github/workflows/ci.yml | 3 --- composer.json | 23 ++++------------------- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb7f810..2b0b287 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,8 +30,6 @@ jobs: with: php-version: ${{ matrix.php }} coverage: xdebug - env: - COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: composer install - run: vendor/bin/phpunit --coverage-text if: ${{ matrix.php >= 7.3 }} @@ -43,7 +41,6 @@ jobs: name: PHPUnit (HHVM) runs-on: ubuntu-18.04 continue-on-error: true - if: false # temporarily skipped until https://github.com/azjezz/setup-hhvm/issues/3 is addressed steps: - uses: actions/checkout@v2 - uses: azjezz/setup-hhvm@v1 diff --git a/composer.json b/composer.json index 90d87d8..32aa713 100644 --- a/composer.json +++ b/composer.json @@ -28,13 +28,12 @@ "require": { "php": ">=5.3.0", "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "react/event-loop": "dev-master#78f7f43 as 1.2.0", - "react/stream": "dev-default-loop#e617d63 as 1.2.0" + "react/event-loop": "^1.2", + "react/stream": "^1.2" }, "require-dev": { "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", - "react/dns": "dev-default-loop#28e5df1 as 1.8.0", - "react/socket": "dev-default-loop#b471dc7 as 1.8.0", + "react/socket": "^1.8", "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" }, "autoload": { @@ -42,19 +41,5 @@ }, "autoload-dev": { "psr-4": { "React\\Tests\\ChildProcess\\": "tests" } - }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/clue-labs/dns" - }, - { - "type": "vcs", - "url": "https://github.com/clue-labs/socket" - }, - { - "type": "vcs", - "url": "https://github.com/clue-labs/stream" - } - ] + } }