Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/Build/ParallelEntryWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Comment on lines 129 to +133
$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);
}
}

Expand Down
5 changes: 1 addition & 4 deletions src/Build/ParallelTaskRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Comment on lines 72 to 75

$count = 0;
Expand Down
39 changes: 39 additions & 0 deletions src/Build/WorkerProcessStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace YiiPress\Build;

use RuntimeException;

use function function_exists;
use function pcntl_wexitstatus;
use function pcntl_wifexited;
use function pcntl_wifsignaled;
use function pcntl_wtermsig;
use function sprintf;

final class WorkerProcessStatus
{
public static function assertSucceeded(int $pid, int $status): void
{
if (function_exists('pcntl_wifexited') && pcntl_wifexited($status)) {
$exitCode = pcntl_wexitstatus($status);
if ($exitCode === 0) {
return;
}

throw new RuntimeException(sprintf('Worker process %d exited with code %d.', $pid, $exitCode));
}

if (function_exists('pcntl_wifsignaled') && pcntl_wifsignaled($status)) {
throw new RuntimeException(sprintf(
'Worker process %d was terminated by signal %d.',
$pid,
pcntl_wtermsig($status),
));
}
Comment on lines +20 to +35

throw new RuntimeException(sprintf('Worker process %d did not exit normally.', $pid));
}
}
39 changes: 37 additions & 2 deletions tests/Unit/Build/ParallelEntryWriterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
use PHPUnit\Framework\TestCase;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RuntimeException;
use SplFileInfo;

use function PHPUnit\Framework\assertFileExists;
use function PHPUnit\Framework\assertSame;
use function PHPUnit\Framework\assertStringContainsString;
use function getmypid;
use function posix_kill;
use function sys_get_temp_dir;

final class ParallelEntryWriterTest extends TestCase
Expand Down Expand Up @@ -102,12 +105,44 @@ public function testWriteFallsBackToSequentialForTinyTaskSet(): void
assertStringContainsString('Only Post', (string) file_get_contents($tasks[0]['filePath']));
}

private function createPipeline(): ContentProcessorPipeline
public function testWriteFailsWhenParallelWorkerIsTerminatedBySignal(): void
{
$tasks = [];
Comment on lines +108 to +110
for ($i = 0; $i < 128; $i++) {
$title = $i === 0 ? 'Kill Worker' : 'Post ' . $i;
$entry = $this->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 '<p>' . $content . '</p>';
}
},
Expand Down
23 changes: 23 additions & 0 deletions tests/Unit/Build/ParallelTaskRunnerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use YiiPress\Build\ParallelTaskRunner;
use PHPUnit\Framework\TestCase;
use RuntimeException;

use function array_unique;
use function count;
Expand All @@ -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;
Expand Down Expand Up @@ -67,4 +69,25 @@ static function () use ($pidFile): int {
}
}
}

public function testRunFailsWhenWorkerIsTerminatedBySignal(): void
{
$runner = new ParallelTaskRunner();

Comment on lines +73 to +76
$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,
);
}
}