From 4d4841f449c92313fe5b873a57ef03136e513fa4 Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Sun, 10 Dec 2017 15:37:33 -0600 Subject: [PATCH] Refactor Process to make writing child processes simple --- bin/worker | 83 ------------------------- examples/blocking-process.php | 21 +++++++ examples/process.php | 30 +++++++++ lib/Context/Internal/process-runner.php | 80 ++++++++++++++++++++++++ lib/Context/Process.php | 30 ++++++++- lib/Worker/Internal/worker-process.php | 37 +++++++++++ lib/Worker/WorkerProcess.php | 11 ++-- test/Context/ProcessTest.php | 24 ++++++- test/Context/test-process.php | 11 ++++ test/bin/process | 61 ------------------ 10 files changed, 235 insertions(+), 153 deletions(-) delete mode 100644 bin/worker create mode 100644 examples/blocking-process.php create mode 100755 examples/process.php create mode 100644 lib/Context/Internal/process-runner.php create mode 100644 lib/Worker/Internal/worker-process.php create mode 100644 test/Context/test-process.php delete mode 100644 test/bin/process diff --git a/bin/worker b/bin/worker deleted file mode 100644 index f6093cf..0000000 --- a/bin/worker +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env php -run()); - } catch (Sync\ChannelException $exception) { - exit(1); // Parent context died, simply exit. - } catch (Throwable $exception) { - $result = new Sync\ExitFailure($exception); - } - - try { - yield $channel->send($result); - } catch (Throwable $exception) { - exit(1); // Parent context died, simply exit. - } -}); diff --git a/examples/blocking-process.php b/examples/blocking-process.php new file mode 100644 index 0000000..4bcdd82 --- /dev/null +++ b/examples/blocking-process.php @@ -0,0 +1,21 @@ +receive()); + + print "Sleeping for 3 seconds...\n"; + sleep(3); // Blocking call in process. + + yield $channel->send("Data sent from child."); + + print "Sleeping for 2 seconds...\n"; + sleep(2); // Blocking call in process. + + return 42; +}; diff --git a/examples/process.php b/examples/process.php new file mode 100755 index 0000000..21b810e --- /dev/null +++ b/examples/process.php @@ -0,0 +1,30 @@ +#!/usr/bin/env php +send("Start data"); // Data sent to child process, received on line 9 of blocking-process.php + + printf("Received the following from child: %s\n", yield $context->receive()); // Sent on line 14 of blocking-process.php + printf("Process ended with value %d!\n", yield $context->join()); + } finally { + Loop::cancel($timer); + } +}); diff --git a/lib/Context/Internal/process-runner.php b/lib/Context/Internal/process-runner.php new file mode 100644 index 0000000..988e006 --- /dev/null +++ b/lib/Context/Internal/process-runner.php @@ -0,0 +1,80 @@ +send($result); + } catch (\Throwable $exception) { + exit(1); // Parent context died, simply exit. + } +}); diff --git a/lib/Context/Process.php b/lib/Context/Process.php index 3184097..9b6cadc 100644 --- a/lib/Context/Process.php +++ b/lib/Context/Process.php @@ -13,6 +13,8 @@ use function Amp\asyncCall; use function Amp\call; class Process implements Context { + const SCRIPT_PATH = __DIR__ . "/Internal/process-runner.php"; + /** @var string|null Cached path to located PHP binary. */ private static $binaryPath; @@ -23,11 +25,28 @@ class Process implements Context { private $channel; /** + * Creates and starts the process at the given path using the optional PHP binary path. + * * @param string|array $script Path to PHP script or array with first element as path and following elements options - * to the PHP script (e.g.: ['bin/worker', '-eOptionValue', '-nOptionValue']. + * to the PHP script (e.g.: ['bin/worker', 'Option1Value', 'Option2Value']. + * @param string $binary Path to PHP binary. Null will attempt to automatically locate the binary. + * + * @return \Amp\Parallel\Context\Process + */ + public static function spawn($script, string $binary = null): self { + $process = new self($script, $binary); + $process->start(); + return $process; + } + + /** + * @param string|array $script Path to PHP script or array with first element as path and following elements options + * to the PHP script (e.g.: ['bin/worker', 'Option1Value', 'Option2Value']. * @param string $binary Path to PHP binary. Null will attempt to automatically locate the binary. * @param string $cwd Working directory. * @param mixed[] $env Array of environment variables. + * + * @throws \Error If the PHP binary path given cannot be found or is not executable. */ public function __construct($script, string $binary = null, string $cwd = "", array $env = []) { $options = [ @@ -52,7 +71,12 @@ class Process implements Context { throw new \Error(\sprintf("The PHP binary path '%s' was not found or is not executable", $binary)); } - $command = \escapeshellarg($binary) . " " . $this->formatOptions($options) . " " . $script; + $command = \implode(" ", [ + \escapeshellarg($binary), + $this->formatOptions($options), + self::SCRIPT_PATH, + $script, + ]); $this->process = new BaseProcess($command, $cwd, $env); } @@ -73,7 +97,7 @@ class Process implements Context { $result = []; foreach ($options as $option => $value) { - $result[] = \sprintf("-d %s=%s", $option, $value); + $result[] = \sprintf("-d%s=%s", $option, $value); } return \implode(" ", $result); diff --git a/lib/Worker/Internal/worker-process.php b/lib/Worker/Internal/worker-process.php new file mode 100644 index 0000000..2437465 --- /dev/null +++ b/lib/Worker/Internal/worker-process.php @@ -0,0 +1,37 @@ +run(); +}; diff --git a/lib/Worker/WorkerProcess.php b/lib/Worker/WorkerProcess.php index f595176..e1a9c3b 100644 --- a/lib/Worker/WorkerProcess.php +++ b/lib/Worker/WorkerProcess.php @@ -8,19 +8,22 @@ use Amp\Parallel\Context\Process; * A worker thread that executes task objects. */ class WorkerProcess extends AbstractWorker { + const SCRIPT_PATH = __DIR__ . "/Internal/worker-process.php"; + /** * @param string|null $binary Path to PHP binary. Null will attempt to automatically locate the binary. * @param string $envClassName Name of class implementing \Amp\Parallel\Worker\Environment to instigate. * Defaults to \Amp\Parallel\Worker\BasicEnvironment. * @param mixed[] $env Array of environment variables to pass to the worker. Empty array inherits from the current * PHP process. See the $env parameter of \Amp\Process\Process::__construct(). + * + * @throws \Error If the PHP binary path given cannot be found or is not executable. */ public function __construct(string $binary = null, string $envClassName = BasicEnvironment::class, array $env = []) { - $dir = \dirname(__DIR__, 2) . '/bin'; $script = [ - $dir . "/worker", - "-e" . $envClassName, + self::SCRIPT_PATH, + $envClassName, ]; - parent::__construct(new Process($script, $binary, $dir, $env)); + parent::__construct(new Process($script, $binary, __DIR__, $env)); } } diff --git a/test/Context/ProcessTest.php b/test/Context/ProcessTest.php index 79d6ff8..1309802 100644 --- a/test/Context/ProcessTest.php +++ b/test/Context/ProcessTest.php @@ -9,10 +9,30 @@ use Amp\Promise; class ProcessTest extends TestCase { public function testBasicProcess() { $process = new Process([ - dirname(__DIR__) . "/bin/process", - "-sTest" + __DIR__ . "/test-process.php", + "Test" ]); $process->start(); $this->assertSame("Test", Promise\wait($process->join())); } + + /** + * @expectedException \Amp\Parallel\Sync\PanicError + * @expectedExceptionMessage No string provided + */ + public function testFailingProcess() { + $process = new Process(__DIR__ . "/test-process.php"); + $process->start(); + Promise\wait($process->join()); + } + + /** + * @expectedException \Amp\Parallel\Sync\PanicError + * @expectedExceptionMessage No script found at 'test-process.php' + */ + public function testInvalidScriptPath() { + $process = new Process("test-process.php"); + $process->start(); + Promise\wait($process->join()); + } } diff --git a/test/Context/test-process.php b/test/Context/test-process.php new file mode 100644 index 0000000..0844b94 --- /dev/null +++ b/test/Context/test-process.php @@ -0,0 +1,11 @@ +send($result); - } catch (Throwable $exception) { - exit(1); // Parent context died, simply exit. - } -});