fix(browse): tame the macOS headless GPU spin + reap the lock-less headless Chromium on stop (#2709)

Two defects in one report. On macOS 26 / Apple Silicon the headless-shell GPU
process pegs ~800% CPU indefinitely after real page work and --disable-gpu
alone is not enough; the reporter validated that adding
--disable-software-rasterizer/--disable-gpu-compositing/--disable-gpu-watchdog
drops it to 0.0% with screenshots still working. The flag block is a pure
platform-parameterized function (unit-tested on any host), darwin-gated,
headless-only (buildGStackLaunchArgs feeds the headed/GBrowser paths where
GPU-off is wrong), with a GSTACK_DISABLE_GPU=off escape.

Separately: the headless launch has no userDataDir, so it never writes the
SingletonLock that killOrphanChromium walks — 'browse stop' reported success
while the orphan kept spinning. The daemon now records the launched child's
pid + wall-clock start time in the state file (the xvfbPid/xvfbStartTime
contract), and stop paths reap a survivor only after verifying BOTH the
recorded start time and a Chromium-looking cmdline — a recycled PID, even one
running a different legitimate Chromium, is never killed (identity tests
include the coreutils-shebang trap that defeats argv0 renames).

macOS efficacy is per the reporter's validation; live re-verification on
Apple silicon is tracked in TODOS.md.

Refs #2709

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-31 21:34:38 +00:00
co-authored by Claude Fable 5
parent 079b36c7f8
commit 78cd06e157
4 changed files with 205 additions and 0 deletions
+8
View File
@@ -3188,6 +3188,14 @@ export async function start() {
// daemon launch on this state file) can validate-then-cleanup orphans
// without clobbering a recycled PID.
...(xvfb ? { xvfbPid: xvfb.pid, xvfbStartTime: xvfb.startTime, xvfbDisplay: xvfb.display } : {}),
// #2709: launched-Chromium identity (pid + start time) so `browse stop`
// can reap a survivor — the headless launch has no SingletonLock for
// killOrphanChromium to walk, and on macOS 26 the orphaned GPU process
// kept spinning at ~800% CPU after the daemon exited.
...(() => {
const info = browserManager.getChromiumProcInfo();
return info ? { chromiumPid: info.pid, chromiumStartTime: info.startTime } : {};
})(),
};
const tmpFile = tmpStatePath();
fs.writeFileSync(tmpFile, JSON.stringify(state, null, 2), { mode: 0o600 });