From 1e156375a8eb1addafe2b9b83118b9bcce188101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Schr=C3=B6er?= Date: Sat, 3 Aug 2024 19:20:43 +0200 Subject: [PATCH] BUGFIX fix race-condition which sometimes blocks the proper script-exit --- src/PoolManager.php | 8 +++-- src/ProcessManager.php | 2 +- tests/ProcessManagerTest.php | 66 ++++++++++++++++++++++++++++++++---- 3 files changed, 65 insertions(+), 11 deletions(-) diff --git a/src/PoolManager.php b/src/PoolManager.php index 2936802..0d464fc 100644 --- a/src/PoolManager.php +++ b/src/PoolManager.php @@ -16,8 +16,9 @@ public function __construct(?ProcessManagerInterface $processManager = null,) // create the process manager instance if not provided $this->pm = $processManager ?? new ProcessManager(); - // register/override interrupt handler for the main-process + // register/override interrupt- and termination-handler for the main-process pcntl_signal(SIGINT, [$this, 'handleInterrupt']); + pcntl_signal(SIGTERM, [$this, 'handleInterrupt']); // register events $this->pm->onThreadCreate(function (ChildProcessInterface $process) { @@ -34,7 +35,7 @@ public function handleInterrupt(): void $this->pm->sendSignalToChildren(SIGTERM); } - public function execute(int $poolSize, callable $mainLoop, callable $processLoop, ?float $killTimeout = null): int + public function execute(int $poolSize, callable $mainLoop, callable $processLoop, ?float $killTimeout = null): never { while (!$this->interrupted) { // ensure we have enough threads @@ -65,6 +66,7 @@ public function execute(int $poolSize, callable $mainLoop, callable $processLoop // final wait until all children exited after the KILL $this->pm->wait(); - return $status; + // terminate the script with the proper exit-code + exit($status); } } diff --git a/src/ProcessManager.php b/src/ProcessManager.php index dde777b..a4ad662 100644 --- a/src/ProcessManager.php +++ b/src/ProcessManager.php @@ -189,7 +189,7 @@ public function wait(?callable $callback = null, bool $block = true): void // wait for all children to exit $wait = true; - while ($wait && !empty($this->childProcesses)) { + while ($wait && (!empty($this->childProcesses) || !empty($this->childExitQueue))) { // process the exit-queue foreach ($this->childExitQueue as $pid => $status) { if ($pid > 0) { diff --git a/tests/ProcessManagerTest.php b/tests/ProcessManagerTest.php index 6d45d4e..6ce92bf 100644 --- a/tests/ProcessManagerTest.php +++ b/tests/ProcessManagerTest.php @@ -138,6 +138,7 @@ function (ChildProcessInterface $process, ParentProcessInterface $parentProcess) $parentProcess->sendMessage( $factory->create(sprintf('answer from #%d', $i), 'hello') ); + return $message !== null && $message->getTopic() === sprintf("hello my child %d", $i) && $message->getPayload() === 'hello'; @@ -161,7 +162,7 @@ function (ChildProcessInterface $process, ParentProcessInterface $parentProcess) /** * @covers \Sweikenb\Library\Pcntl\ProcessManager::wait */ - public function testWait(): void + public function testWaitBlock(): void { $pm = new ProcessManager(); @@ -169,21 +170,72 @@ public function testWait(): void for ($i = 1; $i <= $numChilds; $i++) { $pm->runProcess(function () use ($i) { usleep($i * 10); - return true; }); } $numClosed = 0; - $numWaitLoops = 0; while ($numChilds > $numClosed) { - $numWaitLoops++; $pm->wait(function () use (&$numClosed) { - $numClosed++; - return $numClosed > 50; + ++$numClosed; + }); + } + + $this->assertSame($numChilds, $numClosed); + } + + /** + * @covers \Sweikenb\Library\Pcntl\ProcessManager::wait + */ + public function testWaitUnblock(): void + { + $pm = new ProcessManager(); + + $numChilds = 100; + for ($i = 1; $i <= $numChilds; $i++) { + $pm->runProcess(function () { + sleep(1); }); } - $this->assertSame(51, $numWaitLoops); + $numClosed = 0; + $pm->wait( + function () use (&$numClosed) { + ++$numClosed; + }, + false + ); + $pm->wait(); + + $this->assertSame(0, $numClosed); + } + + /** + * @covers \Sweikenb\Library\Pcntl\ProcessManager::wait + */ + public function testWaitInlineUnblock(): void + { + $pm = new ProcessManager(); + + $numChilds = 100; + for ($i = 1; $i <= $numChilds; $i++) { + $pm->runProcess(function () { + usleep(10); + }); + } + sleep(1); + + $numClosed = 0; + $pm->wait( + function () use (&$numClosed) { + ++$numClosed; + + return false; + } + ); + $pm->wait(); + + $this->assertGreaterThan(0, $numClosed); + $this->assertLessThan($numChilds, $numClosed); } /**