Skip to content

Commit 395eb46

Browse files
lippserdOoYo0uto
andauthored
Close Chrome process pipes before termination to prevent loop hang (#94)
Chrome spawns renderer and utility sub-processes that inherit the open stdin, stdout, and stderr pipes of the main browser process. When only the main process is terminated via `Process::terminate()`, those child processes keep the inherited file descriptors open. ReactPHP's event loop stays alive watching those streams, so `Loop::run()` never returns and PDF generation hangs indefinitely. Close all process pipes explicitly before calling `terminate()` so the event loop has no remaining streams to watch and can exit cleanly. Co-authored-by: OoYo0uto <253079834+OoYo0uto@users.noreply.github.com>
1 parent 4f2de2a commit 395eb46

1 file changed

Lines changed: 22 additions & 2 deletions

File tree

library/Pdfexport/HeadlessChrome.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ public function asyncToPdf(): PromiseInterface
313313
}
314314

315315
$killer = Loop::addTimer(10, function (TimerInterface $timer) use ($chrome, $deferred) {
316-
$chrome->terminate(6); // SIGABRT
316+
$this->terminateProcess($chrome, 6); // SIGABRT
317317

318318
Logger::error(
319319
'Browser timed out after %d seconds without the expected output',
@@ -346,7 +346,7 @@ public function asyncToPdf(): PromiseInterface
346346
Logger::error('Failed to print PDF. An error occurred: %s', $e);
347347
}
348348

349-
$chrome->terminate();
349+
$this->terminateProcess($chrome);
350350

351351
if (! empty($pdf)) {
352352
$deferred->resolve($pdf);
@@ -689,6 +689,26 @@ private function waitFor(Client $ws, $eventName, ?array $expectedParams = null)
689689
return $params;
690690
}
691691

692+
/**
693+
* Terminate a process and close pipes inherited by subprocesses
694+
*
695+
* Chrome spawns sub-processes that inherit open pipes. Close them before termination
696+
* so the event loop is not kept alive by inherited file descriptors.
697+
*
698+
* @param Process $process The process to terminate
699+
* @param ?int $signal The signal passed to Process::terminate()
700+
*
701+
* @return void
702+
*/
703+
private function terminateProcess(Process $process, ?int $signal = null): void
704+
{
705+
foreach ($process->pipes as $pipe) {
706+
$pipe->close();
707+
}
708+
709+
$process->terminate($signal);
710+
}
711+
692712
/**
693713
* Get the major version number of Chrome or false on failure
694714
*

0 commit comments

Comments
 (0)