Skip to content
Merged
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
1 change: 1 addition & 0 deletions bin/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ debug-dispatch.log
playwright-server.log
screenshot_*
pnpm-lock.yaml
package-lock.json
27 changes: 27 additions & 0 deletions bin/playwright-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,30 @@ process.stdin.on('data', ErrorHandler.wrapHandler(async (chunk) => {
sendFramedResponse({ error: 'LSP framing error: ' + error.message });
}
}));

// The PHP process owns this server. Once it is gone, nothing will ever arrive on
// stdin again and every browser we hold becomes an orphan reparented to init.
// stdin closing covers the cases no PHP-side shutdown hook can reach: SIGKILL,
// a fatal error, or SIGTERM, none of which run register_shutdown_function.
let shuttingDown = false;

async function shutdown(reason) {
if (shuttingDown) return;
shuttingDown = true;

// A browser that refuses to close must not wedge the process: exiting without
// cleanup still lets Chromium notice the broken pipe, hanging here does not.
const watchdog = setTimeout(() => process.exit(1), 5000);
watchdog.unref();

try {
logger.info('Parent gone, shutting down', { reason });
} catch {}

await server.exit();
}

process.stdin.on('end', () => shutdown('stdin end'));
process.stdin.on('close', () => shutdown('stdin close'));
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGHUP', () => shutdown('SIGHUP'));
44 changes: 44 additions & 0 deletions tests/Fixtures/orphan-probe.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

/*
* This file is part of the community-maintained Playwright PHP project.
* It is not affiliated with or endorsed by Microsoft.
*
* (c) 2025-Present - Playwright PHP - https://github.com/playwright-php
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/*
* Probe for OrphanedBrowserTest. Launches a browser, announces it is ready, then
* blocks. The test kills this process without warning and checks that the Node
* bridge and the browser die with it.
*
* Nothing here may register a shutdown hook: the point is to reproduce a process
* that never gets the chance to clean up after itself.
*
* Usage: php orphan-probe.php <ready-file>
*/

require dirname(__DIR__, 2).'/vendor/autoload.php';

use Playwright\Playwright;

$readyFile = $argv[1] ?? null;

if (!is_string($readyFile) || '' === $readyFile) {
fwrite(\STDERR, "usage: orphan-probe.php <ready-file>\n");
exit(1);
}

$context = Playwright::chromium(['headless' => true]);
$context->newPage();

// Announce readiness only once the browser is actually up, so the test never
// races against a half-started process tree.
file_put_contents($readyFile, (string) getmypid());

sleep(300);
Loading
Loading