Repository navigation
Conversation
…off keep-alive connections Two server-side bugs behind flaky browser suites on Linux: 1. PlaywrightNpmServer started 'playwright run-server' through Process::fromShellCommandline() without an exec prefix. PHP runs the string as sh -c, dash forks node instead of replacing itself, and stop() only signals the shell: node and its headless browsers are re-parented to PID 1 after every run. Prefix the command with exec on POSIX and give node two seconds to close its browsers before Symfony escalates to SIGKILL. 2. LaravelHttpServer passed Symfony's headers to amphp unchanged. Symfony strips Content-Length from 1xx/204/304 responses and amphp chunk-encodes any HTTP/1.1 response without one, so a 204 went out with a terminating chunk. Browsers treat a 204 as complete after the headers, leaving the stray bytes on the keep-alive socket; the next request on it fails with ERR_INVALID_HTTP_RESPONSE. Send such responses with Content-Length: 0 and no body. Each change comes with a test that fails without it. Supersedes pestphp#169 and pestphp#211. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Full |
|
+1, confirming the Setup: v5.0.1, Symfony Process 8.1.7, PHP 8.5, Playwright 1.63.0, Debian trixie devcontainer ( Without the fix, the process tree during a run is With just the It would be great to see this merged and tagged. |
|
Verified on a third environment class, and the Same test file, same three runs, same outcome each run (
Each orphan holds ~182 MB RSS. On WSL2 they are reparented to Worth noting for anyone reading the issue: a green run leaks too, and the leak is silent. Five ordinary runs earlier today left five servers and ~730 MB behind before I went looking for them. I have not exercised the keep-alive half of this PR, so the table above speaks only to the process fix. |
|
+1, we ran into the 204 No Content issue and it took a lot of trial and error to figure out what was going on. |
|
+1 ran into the same issue. |
Two independent server-process bugs that together made browser suites flaky on Linux. Both were traced on a real project (Laravel 13, Debian 13 in a container, Playwright 1.62.1) and are reproduced by the two tests added here; each test fails on
5.xand passes with the change. Supersedes #169 and #211 (both target4.x).1.
playwright run-serveroutlives the runPlaywrightNpmServer::start()usesProcess::fromShellCommandline()without anexecprefix. PHP'sproc_openruns the string assh -c "…", and dash (Debian's/bin/sh) forksnodeas a child instead of replacing itself, sostop()only ever signals the shell:stop()sh -c ./node_modules/.bin/playwright run-server …)sh→nodeshexits,nodere-parented to PID 1, keeps running with its browsersexecnodeis the direct childnodeexits, browsers exit with itEvery browser run leaked one server plus a headless Chromium tree; after a day of local runs a host held 65 of them (about 8 GB) and had swapped 50 GB.
#169fixes the same thing by passing an array command (Symfony addsexecitself in that case); this PR keeps the shell command line and adds the prefix on POSIX, which also keeps the relative./node_modules/.bin/playwrightresolution unchanged.stop()now gives node 2 s to close its browsers before Symfony escalates to SIGKILL, which is what #211 asked for.Test:
tests/Unit/Playwright/Servers/PlaywrightNpmServerTest.phpstarts a server through the class, asserts the process it owns isnode …(notsh -c …), and asserts that nothing listening on that port survivesstop(). Skipped on macOS/Windows because it reads/proc.2. Body-less responses corrupt keep-alive connections
LaravelHttpServerhands Symfony's headers to amphp unchanged. Symfony stripsContent-Lengthfrom 1xx/204/304 responses, and amphp'sHttp1Driverchunk-encodes every HTTP/1.1 response that has noContent-Length, so aresponse()->noContent()goes out asRFC 7230 §3.3 forbids a body (and
Transfer-Encoding) on 204/304. Chromium treats the response as complete after the headers and returns the socket to its pool with the five stray bytes unread; the next request on that socket fails withnet::ERR_INVALID_HTTP_RESPONSE(captured with--log-net-log). In the real project the casualty was the Livewire runtime script after aPOST …/impression → 204beacon, so pages silently never booted and only the assertions that depended on JavaScript failed, intermittently, depending on which socket Chromium picked.handleRequest()now sends informational and empty responses withContent-Length: 0and no body, which is what every production web server does. amphp itself should special-case 204/304 the way it already special-cases HEAD (reported separately), but the plugin should not depend on that.Test:
tests/Browser/Server/BodylessResponseTest.phpposts to a 204/304 route withkeepaliveand then loads a script; without the fix the script request fails on the reused connection.Checks
vendor/bin/pest tests/Unit/Playwright/Servers/PlaywrightNpmServerTest.php tests/Browser/Server/BodylessResponseTest.php: fail on5.x, 7 passed with the changevendor/bin/peston Linux: see comment belowphpstan: no errors;rectorandpintclean for the touched files🤖 Generated with Claude Code