Skip to content

Commit e1ca936

Browse files
authored
Merge pull request #230 from MarcHagen/patch-1
Fail if `unserialize` is unable to parse output of child process
2 parents 0fa90e9 + bc87ce6 commit e1ca936

File tree

6 files changed

+50
-11
lines changed

6 files changed

+50
-11
lines changed

phpunit.xml.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
convertWarningsToExceptions="true"
1313
processIsolation="false"
1414
stopOnFailure="false"
15+
beStrictAboutTestsThatDoNotTestAnything="true"
1516
>
1617
<coverage>
1718
<include>

src/Process/ParallelProcess.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,15 @@ public function getOutput()
6868
if (! $this->output) {
6969
$processOutput = $this->process->getOutput();
7070

71-
$this->output = @unserialize(base64_decode($processOutput));
71+
$childResult = @unserialize(base64_decode($processOutput));
7272

73-
if (! $this->output) {
73+
if ($childResult === false || ! array_key_exists('output', $childResult)) {
7474
$this->errorOutput = $processOutput;
75+
76+
return null;
7577
}
78+
79+
$this->output = $childResult['output'];
7680
}
7781

7882
return $this->output;
@@ -83,11 +87,13 @@ public function getErrorOutput()
8387
if (! $this->errorOutput) {
8488
$processOutput = $this->process->getErrorOutput();
8589

86-
$this->errorOutput = @unserialize(base64_decode($processOutput));
90+
$childResult = @unserialize(base64_decode($processOutput));
8791

88-
if (! $this->errorOutput) {
92+
if ($childResult === false || ! array_key_exists('output', $childResult)) {
8993
$this->errorOutput = $processOutput;
90-
}
94+
} else {
95+
$this->errorOutput = $childResult['output'];
96+
}
9197
}
9298

9399
return $this->errorOutput;

src/Process/ProcessCallbacks.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ public function timeout(callable $callback): self
3434

3535
public function triggerSuccess()
3636
{
37+
$output = $this->getOutput();
38+
3739
if ($this->getErrorOutput()) {
3840
$this->triggerError();
3941

4042
return;
4143
}
4244

43-
$output = $this->getOutput();
44-
4545
foreach ($this->successCallbacks as $callback) {
4646
call_user_func_array($callback, [$output]);
4747
}

src/Runtime/ChildRuntime.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
use Spatie\Async\Runtime\ParentRuntime;
44

5+
// php://stdout does not obey output buffering. Any output would break
6+
// unserialization of child process results in the parent process.
7+
if (!defined('STDOUT')) {
8+
define('STDOUT', fopen('php://temp', 'w+b'));
9+
define('STDERR', fopen('php://stderr', 'wb'));
10+
}
11+
12+
ini_set('display_startup_errors', 1);
13+
ini_set('display_errors', 'stderr');
14+
515
try {
616
$autoloader = $argv[1] ?? null;
717
$serializedClosure = $argv[2] ?? null;
@@ -23,9 +33,11 @@
2333

2434
$task = ParentRuntime::decodeTask($serializedClosure);
2535

36+
ob_start();
2637
$output = call_user_func($task);
38+
ob_end_clean();
2739

28-
$serializedOutput = base64_encode(serialize($output));
40+
$serializedOutput = base64_encode(serialize(['output' => $output]));
2941

3042
if (strlen($serializedOutput) > $outputLength) {
3143
throw \Spatie\Async\Output\ParallelError::outputTooLarge($outputLength);
@@ -39,7 +51,7 @@
3951

4052
$output = new \Spatie\Async\Output\SerializableException($exception);
4153

42-
fwrite(STDERR, base64_encode(serialize($output)));
54+
fwrite(STDERR, base64_encode(serialize(['output' => $output])));
4355

4456
exit(1);
4557
}

tests/ChildRuntimeTest.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ public function it_can_run()
1515
$autoloader = __DIR__.'/../vendor/autoload.php';
1616

1717
$serializedClosure = base64_encode(serialize(new SerializableClosure(function () {
18-
echo 'child';
18+
echo 'interfere with output';
19+
return 'child';
1920
})));
2021

2122
$process = new Process([
@@ -28,7 +29,10 @@ public function it_can_run()
2829
$process->start();
2930

3031
$process->wait();
32+
$output = unserialize(base64_decode($process->getOutput()));
3133

32-
$this->assertStringContainsString('child', $process->getOutput());
34+
$this->assertIsArray($output);
35+
$this->assertArrayHasKey('output', $output);
36+
$this->assertStringContainsString('child', $output['output']);
3337
}
3438
}

tests/ErrorHandlingTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,22 @@ public function it_handles_stderr_as_parallel_error()
167167
$pool->wait();
168168
}
169169

170+
/** @test */
171+
public function it_handles_stdout_as_parallel_error()
172+
{
173+
$pool = Pool::create();
174+
175+
$pool->add(function () {
176+
fwrite(STDOUT, 'test');
177+
})->then(function ($output) {
178+
$this->fail('Child process output did not error on faulty output');
179+
})->catch(function (ParallelError $error) {
180+
$this->assertStringContainsString('test', $error->getMessage());
181+
});
182+
183+
$pool->wait();
184+
}
185+
170186
/** @test */
171187
public function deep_syntax_errors_are_thrown()
172188
{

0 commit comments

Comments
 (0)