diff --git a/Spawn/Container.php b/Spawn/Container.php index 65c5751..0e30c4f 100644 --- a/Spawn/Container.php +++ b/Spawn/Container.php @@ -44,8 +44,6 @@ exit(0); } catch (\Throwable $exception) { - require_once __DIR__ . \DIRECTORY_SEPARATOR . 'SerializableException.php'; - $output = new SerializableException($exception); $channel->error(\base64_encode(\serialize($output))); diff --git a/Spawn/Launcher.php b/Spawn/Launcher.php index ac8ba8d..573f0ab 100644 --- a/Spawn/Launcher.php +++ b/Spawn/Launcher.php @@ -14,6 +14,7 @@ use Async\Spawn\SpawnError; use Async\Spawn\SerializableException; use Async\Spawn\LauncherInterface; +use UVProcess; /** * Launcher runs a command/script/application/callable in an independent process. @@ -26,6 +27,7 @@ class Launcher implements LauncherInterface * @var Process|\UVProcess */ protected $process; + protected $task; protected $id; protected $pid; protected $in; @@ -65,7 +67,8 @@ private function __construct( \UVPipe $error = null, \UVTimer $timer = null, \UVLoop $loop = null, - bool $isYield = false + bool $isYield = false, + $task = null ) { $this->timeout = $timeout; $this->process = $process; @@ -74,8 +77,9 @@ private function __construct( $this->out = $output; $this->err = $error; $this->timer = $timer; - self::$uv = $loop; $this->isYield = $isYield; + $this->task = $task; + self::$uv = $loop; self::$launcher[$id] = $this; } @@ -125,8 +129,10 @@ public static function add( $id = (int) $getId; $launcher = isset($launch[$id]) ? $launch[$id] : null; if ($launcher instanceof Launcher) { - \uv_idle_stop($launcher->idle); - \uv_unref($launcher->idle); + if ($launcher->idle instanceof \UVIdle && \uv_is_active($launcher->idle)) { + \uv_idle_stop($launcher->idle); + \uv_unref($launcher->idle); + } if ($launcher->timer instanceof \UVTimer && \uv_is_active($launcher->timer)) { \uv_timer_stop($launcher->timer); @@ -134,7 +140,7 @@ public static function add( } if ($signal) { - if ($signal === \UV::SIGABRT) { + if ($signal === \SIGINT) { $launcher->status = 'timeout'; $launcher->triggerTimeout($launcher->isYield); } else { @@ -183,7 +189,8 @@ public static function add( 0 ); } else { - $taskArray = \explode(' ', $task); + $cmd = (\IS_WINDOWS) ? 'cmd /c ' . $task : $task; + $taskArray = \explode(' ', $cmd); $process = \uv_spawn( $uvLoop, \array_shift($taskArray), @@ -196,18 +203,31 @@ public static function add( ); } - $timer = null; + $timer = \uv_timer_init($uvLoop); if ($timeout) { - $timer = \uv_timer_init($uvLoop); \uv_timer_start($timer, $timeout * 1000, 0, function ($timer) use ($process, $getId, &$launch) { $launch[$getId]->status = 'timeout'; - \uv_process_kill($process, \UV::SIGABRT); - \uv_timer_stop($timer); + if ($process instanceof \UVProcess && \uv_is_active($process)) { + \uv_process_kill($process, \SIGINT); + } + + //\uv_timer_stop($timer); \uv_unref($timer); }); } - return new self($process, (int) $getId, $timeout, $in, $out, $err, $timer, $uvLoop, $isYield); + return new self( + $process, + (int) $getId, + $timeout, + $in, + $out, + $err, + $timer, + $uvLoop, + $isYield, + $task + ); } public function start(): LauncherInterface @@ -254,14 +274,21 @@ public function start(): LauncherInterface public function restart(): LauncherInterface { - if ($this->isRunning()) - $this->stop(); + if ($this->process instanceof Process) { + if ($this->isRunning()) + $this->stop(); - $process = clone $this->process; + $process = clone $this->process; + $launcher = $this->create($process, $this->id, $this->timeout); - $launcher = $this->create($process, $this->id, $this->timeout); + return $launcher->start(); + } else { + $launcher = self::add($this->task); + if ($this->isRunning()) + $this->stop(); - return $launcher->start(); + return $launcher->start(); + } } public function run(bool $useYield = false) @@ -365,7 +392,7 @@ protected function checkProcess(bool $useYield = false) public function stop(): LauncherInterface { if ($this->process instanceof \UVProcess) { - \uv_process_kill($this->process, \UV::SIGKILL); + \uv_process_kill($this->process, \SIGKILL); } else { $this->process->stop(); } @@ -529,6 +556,9 @@ public function getId(): int public function getPid(): ?int { + if ($this->process instanceof \UVProcess) + return (int) $this->process; + return $this->pid; } diff --git a/Spawn/LauncherInterface.php b/Spawn/LauncherInterface.php index 07fe449..8b7f7f4 100644 --- a/Spawn/LauncherInterface.php +++ b/Spawn/LauncherInterface.php @@ -164,7 +164,7 @@ public function getErrorOutput(); /** * Returns the Pid (process identifier), if applicable. * - * @return int|null — The process id if running, null otherwise + * @return int|null The process id if running, null otherwise */ public function getPid(): ?int; diff --git a/Spawn/Spawn.php b/Spawn/Spawn.php index 1d4ffae..67909b5 100644 --- a/Spawn/Spawn.php +++ b/Spawn/Spawn.php @@ -189,9 +189,9 @@ public static function uvLoop(\UVLoop $loop) public static function daemon($task, $channel = null): LauncherInterface { if (\is_string($task)) { - $shadow = (('\\' === \DIRECTORY_SEPARATOR) ? 'start /b ' : 'nohup ') . $task; + $shadow = (('\\' === \IS_WINDOWS) ? 'start /b ' : 'nohup ') . $task; } else { - $shadow[] = ('\\' === \DIRECTORY_SEPARATOR) ? 'start /b' : 'nohup'; + $shadow[] = ('\\' === \IS_WINDOWS) ? 'start /b' : 'nohup'; $shadow[] = $task; } diff --git a/examples/spawn.php b/examples/spawn.php index 4c4d67e..2e7dc09 100644 --- a/examples/spawn.php +++ b/examples/spawn.php @@ -17,8 +17,7 @@ function (ChannelInterface $channel) { echo $channel->read(); \usleep(1000); return 'The game!'; - }, - 0 + } )->progress(function ($type, $data) use ($ipc) { if ('ping' === $data) { $ipc->send('pang' . \PHP_EOL); diff --git a/tests/ChannelFallbackTest.php b/tests/ChannelFallbackTest.php index 8a8bbc8..87a9cfa 100644 --- a/tests/ChannelFallbackTest.php +++ b/tests/ChannelFallbackTest.php @@ -22,7 +22,7 @@ public function testSimpleChannel() $channel->write('ping'); echo $channel->read(); echo $channel->read(); - usleep(1000); + usleep(5000); return 9; }, 10, $ipc) ->progress( diff --git a/tests/SpawnTest.php b/tests/SpawnTest.php index 9c3275a..f50eec4 100644 --- a/tests/SpawnTest.php +++ b/tests/SpawnTest.php @@ -2,9 +2,10 @@ namespace Async\Tests; -use Async\Spawn\ChannelInterface; use Async\Spawn\Spawn; use Async\Spawn\SpawnError; +use Async\Spawn\Channel; +use Async\Spawn\ChannelInterface; use PHPUnit\Framework\TestCase; class SpawnTest extends TestCase @@ -25,13 +26,14 @@ public function testIt_can_handle_success() }); $this->assertTrue($process->isRunning()); + $this->assertFalse($process->isTimedOut()); \spawn_run($process); $this->assertFalse($process->isRunning()); $this->assertTrue($process->isSuccessful()); $this->assertEquals(2, $counter); $this->assertEquals(2, \spawn_output($process)); } -/* + public function testIt_can_handle_success_yield() { $counter = 0; @@ -49,30 +51,13 @@ public function testIt_can_handle_success_yield() $this->assertFalse($process->isSuccessful()); $this->assertTrue($process->isRunning()); + $this->assertFalse($process->isTimedOut()); $this->assertEquals(2, $yield->current()); $this->assertFalse($process->isRunning()); $this->assertTrue($process->isSuccessful()); - $process->close(); - } -/* - public function testChainedProcesses() - { - $p1 = spawn(function (ChannelInterface $channel) { - $channel->error(123); - $channel->write(456); - }); - - $p2 = spawn(function (ChannelInterface $channel) { - $channel->passthru(); - }, 5, $p1->getProcess()); - - $p1->start(); - $p2->run(); - - $this->assertSame('123', $p1->getErrorOutput()); - $this->assertSame('', $p1->getProcess()->getOutput()); - $this->assertSame('', $p2->getErrorOutput()); - $this->assertSame('456', $p2->getOutput()); + $this->assertFalse($process->isTerminated()); + $this->assertEquals(2, \spawn_output($process)); + //$this->assertEquals(2, $counter); } public function testIt_can_handle_timeout() @@ -80,16 +65,16 @@ public function testIt_can_handle_timeout() $counter = 0; $process = Spawn::create(function () { - usleep(1000); - }, .5)->timeout(function () use (&$counter) { + usleep(1000000); + }, 1)->timeout(function () use (&$counter) { $counter += 1; }); + $this->assertTrue($process->isRunning()); + $this->assertFalse($process->isTimedOut()); $process->run(); - //var_dump($process->isRunning()); - // $this->assertTrue($process->isTimedOut()); - - $process->close(); + $this->assertTrue($process->isTimedOut()); + $this->assertFalse($process->isRunning()); $this->assertEquals(1, $counter); } @@ -98,16 +83,20 @@ public function testIt_can_handle_timeout_yield() $counter = 0; $process = Spawn::create(function () { - sleep(1000); + usleep(1000000); }, 1, null, true)->timeout(function () use (&$counter) { $counter += 1; }); - $yield = $process->yielding(); + $this->assertTrue($process->isRunning()); $this->assertFalse($process->isTimedOut()); - + $yield = $process->yielding(); + $this->assertTrue($yield instanceof \Generator); $this->assertNull($yield->current()); $this->assertTrue($process->isTimedOut()); + $this->assertFalse($process->isRunning()); + $this->assertFalse($process->isSuccessful()); + $this->assertFalse($process->isTerminated()); //$this->assertEquals(1, $counter); } @@ -119,19 +108,22 @@ public function testStart() $this->assertTrue($process->getProcess() instanceof \UVProcess); $this->assertIsNumeric($process->getId()); - $this->assertFalse($process->isRunning()); + $this->assertTrue($process->isRunning()); $this->assertFalse($process->isTimedOut()); $this->assertFalse($process->isTerminated()); + $this->assertFalse($process->isSuccessful()); $process->start(); $this->assertTrue($process->isRunning()); $this->assertFalse($process->isTimedOut()); $this->assertFalse($process->isTerminated()); + $this->assertFalse($process->isSuccessful()); $process->wait(); $this->assertFalse($process->isRunning()); - $this->assertTrue($process->isTerminated()); + $this->assertTrue($process->isSuccessful()); + $this->assertFalse($process->isTerminated()); + $this->assertFalse($process->isTimedOut()); } -*/ public function testLiveOutput() { $process = Spawn::create(function () { @@ -140,8 +132,6 @@ public function testLiveOutput() }); $this->expectOutputString('hello child'); $process->displayOn()->run(); - - $process->close(); } public function testGetResult() @@ -158,10 +148,10 @@ function () { $p->run(); $this->assertSame('hello child3', $p->getOutput()); $this->assertSame(3, $p->getResult()); - $p->close(); + $this->assertSame(3, $p->getLast()); } -/* - public function testGetOutputShell() + + public function testGetOutputFromShell() { if (\IS_WINDOWS) { // see http://stackoverflow.com/questions/7105433/windows-batch-echo-without-new-line @@ -185,22 +175,22 @@ public function testGetOutput() }); $p->run(); - $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches)); + $this->assertEquals(3, \preg_match_all('/foo/', $p->getOutput(), $matches)); } public function testGetErrorOutput() { - $p = spawn(function () { + $p = \spawn(function () { $n = 0; while ($n < 3) { - file_put_contents('php://stderr', 'ERROR'); + \file_put_contents('php://stderr', 'ERROR'); $n++; } })->catch(function (SpawnError $error) { - $this->assertEquals(3, preg_match_all('/ERROR/', $error->getMessage(), $matches)); + $this->assertEquals(3, \preg_match_all('/ERROR/', $error->getMessage(), $matches)); }); - spawn_run($p); + \spawn_run($p); } public function testGetErrorOutputYield() @@ -208,7 +198,7 @@ public function testGetErrorOutputYield() $p = Spawn::create(function () { $n = 0; while ($n < 3) { - file_put_contents('php://stderr', 'ERROR'); + \file_put_contents('php://stderr', 'ERROR'); $n++; } })->catch(function (SpawnError $error) { @@ -216,28 +206,7 @@ public function testGetErrorOutputYield() }); $yield = $p->yielding(); - $this->assertNull($yield->current()); - } - - public function testRestart() - { - $process1 = Spawn::create(function () { - return getmypid(); - }); - - $this->expectOutputRegex('/[\d]/'); - $process1->displayOn()->run(); - $process2 = $process1->restart(); - - $this->expectOutputRegex('//'); - $process2->displayOff()->wait(); // wait for output - - // Ensure that both processed finished and the output is numeric - $this->assertFalse($process1->isRunning()); - $this->assertFalse($process2->isRunning()); - - // Ensure that restart returned a new process by check that the output is different - $this->assertNotEquals($process1->getOutput(), $process2->getOutput()); + $yield->current(); } public function testWaitReturnAfterRunCMD() @@ -250,10 +219,11 @@ public function testWaitReturnAfterRunCMD() public function testStop() { $process = Spawn::create(function () { - sleep(1000); + \sleep(10); })->start(); $this->assertTrue($process->isRunning()); $process->stop(); + $process->wait(); $this->assertFalse($process->isRunning()); } @@ -267,69 +237,41 @@ public function testIsSuccessfulCMD() public function testGetPid() { $process = Spawn::create(function () { - sleep(1000); - })->start(); - $this->assertGreaterThan(0, $process->getPid()); + sleep(10); + }, 1); $process->stop(); + $this->assertGreaterThan(0, $process->getPid()); + $process->run(); } - public function testPhpPathExecutable() + public function testRestart() { - $executable = '/opt/path/that/can/never/exist/for/testing/bin/php'; - $notFoundError = ''; - $result = null; - - // test with custom executable - Spawn::shell($executable); - $process = Spawn::create(function () { - return true; - })->then(function ($_result) use (&$result) { - $result = $_result; - })->catch(function ($error) use (&$result, &$notFoundError) { - $result = false; - $notFoundError = $error->getMessage(); + $process1 = Spawn::create(function () { + return getmypid(); }); - if (\IS_WINDOWS) { - $pathCheck = 'The system cannot find the path specified.'; - } else { - $pathCheck = $executable; - } - - $process->run(); - $this->assertEquals(false, $result); - $this->assertRegExp("%{$pathCheck}%", $notFoundError); - - // test with default executable (reset for further tests) - Spawn::shell('php'); - $process = Spawn::create(function () { - return 'reset'; - })->then(function ($_result) use (&$result) { - $result = $_result; - }); + $this->expectOutputRegex('/[\d]/'); + $process1->displayOn()->run(); + $process2 = $process1->restart(); - $process->run(); - $this->assertEquals('reset', $result); + $this->expectOutputRegex('//'); + $process2->displayOff()->wait(); // wait for output - // test with default executable - $process = Spawn::create(function () { - return 'default'; - })->then(function ($_result) use (&$result) { - $result = $_result; - }); + // Ensure that both processed finished and the output is numeric + $this->assertFalse($process1->isRunning()); + $this->assertFalse($process2->isRunning()); - $process->run(); - $this->assertEquals('default', $result); + // Ensure that restart returned a new process by check that the output is different + $this->assertNotEquals($process1->getOutput(), $process2->getOutput()); } public function testLargeOutputs() { $process = Spawn::create(function () { - return str_repeat('abcd', 1024 * 512); - }); + return \str_repeat('abcd', 1024 * 512); + }, 1); $process->run(); - $this->assertEquals(str_repeat('abcd', 1024 * 512), $process->getOutput()); + $this->assertEquals(\str_repeat('abcd', 1024 * 512), $process->getOutput()); } - */ }