mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-20 20:00:45 +02:00
fix(browse): Chromium-crash exit is daemon-only — embedded launches never kill their host
handleChromiumDisconnect unconditionally process.exit()ed. Correct for
the standalone daemon (its supervisor/user must notice); suicidal when
a TEST launches BrowserManager in-process: a mid-suite Chromium death
exited the whole bun shard with no terminal summary — the exact
truncation class the strict runner flags (observed live: CI shard 1 on
eb233299 died at cache-concurrent-refresh right after a daemon-spawning
gate test; with this fix the same pairing runs to completion and
REPORTS instead of dying).
The standalone entrypoint opts in via markDaemonProcess() under
server.ts's import.meta.main gate — the same embedder contract its
signal handlers already use (gbrowser phoenix keeps its own handlers).
Embedded contexts now get the disconnect log line and continue.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -186,12 +186,32 @@ export async function resolveDisconnectCause(browser: Browser | null): Promise<'
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Headless `launch()` disconnect handler. Exits 0 on clean user-quit, 1 on
|
* Exit-on-disconnect is DAEMON-ONLY semantics. The standalone server
|
||||||
* crash. Inlined into the launch() body via a one-line dispatch so
|
* entrypoint opts in via markDaemonProcess() (under its import.meta.main
|
||||||
|
* gate, same contract as its signal handlers); embedders — gbrowser
|
||||||
|
* phoenix, and every test that launches a BrowserManager in-process —
|
||||||
|
* must never have a Chromium crash process.exit() their HOST. Observed
|
||||||
|
* live before this flag: a test-launched browser died mid-suite and the
|
||||||
|
* exit(1) killed the whole bun shard with no terminal summary (the
|
||||||
|
* truncation class the strict runner exists to catch).
|
||||||
|
*/
|
||||||
|
let daemonProcess = false;
|
||||||
|
export function markDaemonProcess(): void {
|
||||||
|
daemonProcess = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Headless `launch()` disconnect handler. In the standalone daemon: exits 0
|
||||||
|
* on clean user-quit, 1 on crash. Embedded contexts get the log line only.
|
||||||
|
* Inlined into the launch() body via a one-line dispatch so
|
||||||
* browser-manager's flow stays grep-friendly.
|
* browser-manager's flow stays grep-friendly.
|
||||||
*/
|
*/
|
||||||
export async function handleChromiumDisconnect(browser: Browser | null): Promise<void> {
|
export async function handleChromiumDisconnect(browser: Browser | null): Promise<void> {
|
||||||
const cause = await resolveDisconnectCause(browser);
|
const cause = await resolveDisconnectCause(browser);
|
||||||
|
if (!daemonProcess) {
|
||||||
|
console.error(`[browse] Chromium disconnected (${cause}) in an embedded context — host process continues.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (cause === 'clean') {
|
if (cause === 'clean') {
|
||||||
console.error('[browse] Chromium closed cleanly (user-initiated quit). Server exiting (0).');
|
console.error('[browse] Chromium closed cleanly (user-initiated quit). Server exiting (0).');
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
* Port: random 10000-60000 (or BROWSE_PORT env for debug override)
|
* Port: random 10000-60000 (or BROWSE_PORT env for debug override)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { BrowserManager } from './browser-manager';
|
import { BrowserManager, markDaemonProcess } from './browser-manager';
|
||||||
import { handleReadCommand, hasOutArg } from './read-commands';
|
import { handleReadCommand, hasOutArg } from './read-commands';
|
||||||
import { handleWriteCommand } from './write-commands';
|
import { handleWriteCommand } from './write-commands';
|
||||||
import { handleMetaCommand } from './meta-commands';
|
import { handleMetaCommand } from './meta-commands';
|
||||||
@@ -1394,6 +1394,10 @@ async function handleCommand(body: any, tokenInfo?: TokenInfo | null): Promise<R
|
|||||||
// server.ts as a submodule can register their own signal handlers without
|
// server.ts as a submodule can register their own signal handlers without
|
||||||
// fighting with gstack's. CLI path is unchanged.
|
// fighting with gstack's. CLI path is unchanged.
|
||||||
if (import.meta.main) {
|
if (import.meta.main) {
|
||||||
|
// Standalone daemon: a Chromium crash must exit THIS process (its
|
||||||
|
// supervisor/user notices); embedders and in-process test launches must
|
||||||
|
// never be exited by browser-manager's disconnect handler.
|
||||||
|
markDaemonProcess();
|
||||||
// SIGINT (Ctrl+C): user intentionally stopping → shutdown.
|
// SIGINT (Ctrl+C): user intentionally stopping → shutdown.
|
||||||
process.on('SIGINT', () => activeShutdown?.());
|
process.on('SIGINT', () => activeShutdown?.());
|
||||||
// SIGHUP (terminal hangup): with handleSIGHUP:false at the three launch
|
// SIGHUP (terminal hangup): with handleSIGHUP:false at the three launch
|
||||||
|
|||||||
Reference in New Issue
Block a user