fix(engine): a Chrome shell that ignores SIGTERM can no longer freeze the CLI at launch (#6547)

The start probe waited for the shell to exit after SIGTERM, so a shell that stalled under load blocked the whole process. Kill it outright at the timeout.
This commit is contained in:
Miguel Ángel
2026-10-27 11:09:05 +00:00
committed by GitHub
parent 4f1e66d2d0
commit 2634a343dd
2 changed files with 24 additions and 3 deletions
@@ -1,5 +1,8 @@
import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { chromeMajorCeiling, exceedsChromeCeiling } from "./chromeHostCeiling.js";
import { chromeMajorCeiling, exceedsChromeCeiling, shellStarts } from "./chromeHostCeiling.js";
describe("chromeMajorCeiling", () => {
it.each([
@@ -22,3 +25,19 @@ describe("exceedsChromeCeiling", () => {
expect(exceedsChromeCeiling(name, ceiling)).toBe(expected);
});
});
describe.skipIf(process.platform === "win32")("shellStarts", () => {
it("returns at its timeout even when the shell ignores SIGTERM", () => {
const dir = mkdtempSync(join(tmpdir(), "hf-shell-probe-"));
const binary = join(dir, "shell.sh");
writeFileSync(binary, "#!/bin/sh\ntrap '' TERM\nsleep 6\n");
chmodSync(binary, 0o755);
const started = Date.now();
try {
expect(shellStarts(binary, 300)).toBe(false);
expect(Date.now() - started).toBeLessThan(3000);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
});
@@ -64,11 +64,13 @@ const starts = new Map<string, boolean>();
const debug = debuglog("hyperframes:browser");
/** Whether a shell loads a blank page and exits: an old build can die at launch on a newer OS. Asked once per path. */
export function shellStarts(binary: string): boolean {
export function shellStarts(binary: string, timeoutMs = 10_000): boolean {
let answer = starts.get(binary);
if (answer === undefined) {
const probe = ["--no-sandbox", "--dump-dom", "about:blank"];
answer = spawnSync(binary, probe, { stdio: "ignore", timeout: 10_000 })?.status === 0;
answer =
spawnSync(binary, probe, { stdio: "ignore", timeout: timeoutMs, killSignal: "SIGKILL" })
?.status === 0;
if (!answer) debug("skipping %s: it did not start", binary);
starts.set(binary, answer);
}