[Bug]: browser plugin — Playwright server survives the parent on SIGTERM (the signal CI uses to cancel)
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 11.7k
- Forks
- 538
- Avg merge
- 4d 11h
- Merged PRs (30d)
- 8
Description
Filed here because
pestphp/pest-plugin-browserhas issues disabled.
Pest: v5.0.3 · Plugin: pest-plugin-browser v5.0.0 · PHP: 8.5.8 · OS: macOS
Summary
The Playwright node server survives its parent process on SIGTERM, which is the signal CI jobs and container runtimes use to cancel a run. Each cancelled run leaves a playwright run-server process behind holding its port and its memory; a machine that cancels runs regularly accumulates them.
SIGINT is fine — that path already cleans up. It is specifically SIGTERM that leaks, and that is the one a cancelled CI job sends.
Measurement
ServerManager::instance()->playwright() + start(), then the parent is signalled. Counting playwright run-server processes before and after (v5.0.0, macOS, PHP 8.5.8):
| signal | parent alive after | server before | server after |
|---|---|---|---|
| SIGINT | no | 1 | 0 ✅ |
| SIGTERM | no | 1 | 1 ❌ |
| SIGKILL | no | 1 | 1 (expected — uncatchable) |
Why
The only path that stops the server is Plugin::terminate() → PlaywrightNpmServer::stop(), plus Symfony's Process destructor. PHP CLI runs the shutdown sequence for SIGINT, so both fire. For SIGTERM it terminates without running shutdown functions or destructors, so neither fires and the child is orphaned.
There is no pcntl_signal() handler anywhere in src/ — I grepped for SIGTERM / SIGINT / SIGHUP and the plugin registers none. So this is not a race or a timing window; the signal simply has no handler.
Suggested direction
Register a SIGTERM (and SIGHUP) handler when pcntl is available, running the same teardown terminate() already does, and re-raise the default. pcntl is not always present, so the handler needs to be conditional — but where it is absent the behavior is exactly what happens today, so this is strictly an improvement.
I did not open a PR for this one because the right place to hook it (the plugin's terminate(), the server class itself, or a small signal facade) is a structural call I would rather leave to you. Happy to write it if you name the shape you want.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the SIGINT, SIGTERM, and SIGKILL behavior described for ServerManager::instance()->playwright()->start(), then inspect Plugin::terminate() and PlaywrightNpmServer::stop() in src/. Check the existing process teardown path and the absence of signal handling. Done means SIGTERM and SIGHUP clean up the server when pcntl is available, while SIGKILL remains uncatchable and behavior without pcntl is unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php
- Domain
- testing
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100