diff --git a/src/Build/ParallelEntryWriter.php b/src/Build/ParallelEntryWriter.php index 01644fc..01fbbe5 100644 --- a/src/Build/ParallelEntryWriter.php +++ b/src/Build/ParallelEntryWriter.php @@ -21,7 +21,6 @@ use function function_exists; use function min; use function pcntl_fork; -use function pcntl_wexitstatus; use function pcntl_waitpid; final readonly class ParallelEntryWriter @@ -126,15 +125,19 @@ private function writeParallel(SiteConfig $siteConfig, array $tasks, string $con } $failed = false; + $failure = null; foreach ($pids as $pid) { pcntl_waitpid($pid, $status); - if (pcntl_wexitstatus($status) !== 0) { + try { + WorkerProcessStatus::assertSucceeded($pid, $status); + } catch (RuntimeException $e) { $failed = true; + $failure ??= $e; } } if ($failed) { - throw new RuntimeException('One or more worker processes failed'); + throw new RuntimeException('One or more worker processes failed.', previous: $failure); } } diff --git a/src/Build/ParallelTaskRunner.php b/src/Build/ParallelTaskRunner.php index 6997f36..46fb716 100644 --- a/src/Build/ParallelTaskRunner.php +++ b/src/Build/ParallelTaskRunner.php @@ -16,7 +16,6 @@ use function mkdir; use function min; use function pcntl_fork; -use function pcntl_wexitstatus; use function pcntl_waitpid; use function rmdir; use function sys_get_temp_dir; @@ -72,9 +71,7 @@ public function run(array $tasks, int $workerCount, callable $taskRunner, int $m foreach ($pids as $pid) { pcntl_waitpid($pid, $status); - if (pcntl_wexitstatus($status) !== 0) { - throw new RuntimeException('One or more worker processes failed'); - } + WorkerProcessStatus::assertSucceeded($pid, $status); } $count = 0; diff --git a/src/Build/WorkerProcessStatus.php b/src/Build/WorkerProcessStatus.php new file mode 100644 index 0000000..0b7237e --- /dev/null +++ b/src/Build/WorkerProcessStatus.php @@ -0,0 +1,39 @@ +createEntry('entry-' . $i, $title); + $tasks[] = [ + 'entry' => $entry, + 'filePath' => $this->outputDir . '/blog/entry-' . $i . '/index.html', + 'permalink' => '/blog/entry-' . $i . '/', + ]; + } + + $writer = new ParallelEntryWriter($this->createPipeline(killOnTitle: 'Kill Worker'), $this->createTemplateResolver()); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('One or more worker processes failed.'); + + try { + $writer->write($this->createSiteConfig(), $tasks, $this->contentDir, 2); + } catch (RuntimeException $e) { + assertStringContainsString('terminated by signal', (string) $e->getPrevious()?->getMessage()); + throw $e; + } + } + + private function createPipeline(string $killOnTitle = ''): ContentProcessorPipeline { return new ContentProcessorPipeline( - new class () implements ContentProcessorInterface { + new readonly class ($killOnTitle) implements ContentProcessorInterface { + public function __construct(private string $killOnTitle) {} + public function process(string $content, Entry $entry): string { + if ($entry->title === $this->killOnTitle) { + posix_kill(getmypid(), \SIGKILL); + } + return '
' . $content . '
'; } }, diff --git a/tests/Unit/Build/ParallelTaskRunnerTest.php b/tests/Unit/Build/ParallelTaskRunnerTest.php index 0946bde..877199f 100644 --- a/tests/Unit/Build/ParallelTaskRunnerTest.php +++ b/tests/Unit/Build/ParallelTaskRunnerTest.php @@ -6,6 +6,7 @@ use YiiPress\Build\ParallelTaskRunner; use PHPUnit\Framework\TestCase; +use RuntimeException; use function array_unique; use function count; @@ -15,6 +16,7 @@ use function is_file; use function PHPUnit\Framework\assertNotFalse; use function PHPUnit\Framework\assertSame; +use function posix_kill; use function sys_get_temp_dir; use function uniqid; use function unlink; @@ -67,4 +69,25 @@ static function () use ($pidFile): int { } } } + + public function testRunFailsWhenWorkerIsTerminatedBySignal(): void + { + $runner = new ParallelTaskRunner(); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('terminated by signal'); + + $runner->run( + [1, 2], + 2, + static function (int $task): int { + if ($task === 1) { + posix_kill(getmypid(), \SIGKILL); + } + + return 1; + }, + minTasksPerWorker: 1, + ); + } }