Skip to content

Commit 313a319

Browse files
committed
test(cli): windows orphan proof with creation-time guard
1 parent 97e27d9 commit 313a319

3 files changed

Lines changed: 20 additions & 6 deletions

File tree

‎.github/checkfast/winlaunch.sh‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
set -u
22
node --import tsx packages/cli/scripts/prepare-process-test-browser.mjs; export HYPERFRAMES_BROWSER_PATH=$(grep HYPERFRAMES_BROWSER_PATH "$GITHUB_ENV" | tail -1 | cut -d= -f2-)
33
cd packages/cli
4-
for i in 1 2 3; do echo "=== ORPH run $i"; npx vitest run src/browser/chromeOrphans.process.test.ts --maxWorkers=1 2>&1 | sed 's/\x1b\[[0-9;]*m//g' | grep -E "✓|×|Tests |AssertionError|Error:|^\+ " ; done
4+
r() { npx vitest run src/browser/chromeOrphans.process.test.ts --maxWorkers=1 2>&1 | sed 's/\x1b\[[0-9;]*m//g' | grep -E "✓|×|Tests |AssertionError|^\+ "; }
5+
for i in 1 2 3; do echo "=== ORPH run $i"; r; done
6+
cp src/browser/windowsChromeWatchdog.ts /tmp/wd.ts
7+
sed -i 's/ HF_CHROME_SINCE: String(launchedAt),/ HF_CHROME_SINCE: String(launchedAt + 3_600_000),/' src/browser/windowsChromeWatchdog.ts
8+
echo "=== MUTANT start time in the future ($(grep -c '3_600_000' src/browser/windowsChromeWatchdog.ts))"; r
9+
cp /tmp/wd.ts src/browser/windowsChromeWatchdog.ts
510
sed -i 's/ if (!executable || chrome.pid === undefined) return undefined;/ return undefined;/' src/browser/windowsChromeWatchdog.ts
6-
echo "=== MUTANT no watchdog ($(grep -c '^ return undefined;$' src/browser/windowsChromeWatchdog.ts))"; npx vitest run src/browser/chromeOrphans.process.test.ts --maxWorkers=1 2>&1 | sed 's/\x1b\[[0-9;]*m//g' | grep -E "✓|×|Tests |AssertionError|^\+ "
11+
echo "=== MUTANT no watchdog ($(grep -c '^ return undefined;$' src/browser/windowsChromeWatchdog.ts))"; r

‎packages/cli/src/browser/launch.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ async function launchOwnedBrowser(
112112
ownSignals();
113113
const abort = new AbortController();
114114
const signal = options.signal ? AbortSignal.any([options.signal, abort.signal]) : abort.signal;
115+
const launchedAt = Date.now();
115116
const launch = untilLaunchOrAbort(
116117
puppeteer.launch({ ...options, pipe: true, signal, handleSIGINT: false, handleSIGTERM: false }),
117118
signal,
@@ -130,7 +131,7 @@ async function launchOwnedBrowser(
130131
const child = browser.process();
131132
if (!child) throw new Error("A launched browser has no owned process.");
132133
browsers.add(browser);
133-
const watchdog = watchChromeTree(child);
134+
const watchdog = watchChromeTree(child, launchedAt);
134135
if (watchdog) watchdogs.set(browser, watchdog);
135136
child.once("exit", () => browsers.delete(browser));
136137
return browser;

‎packages/cli/src/browser/windowsChromeWatchdog.ts‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { spawn, type ChildProcess } from "node:child_process";
22
import { realpathSync } from "node:fs";
33

4-
const SWEEP = `$b=[int]$env:HF_CHROME_PID; $x=$env:HF_CHROME_EXE; Get-CimInstance Win32_Process | Where-Object { ($_.ProcessId -eq $b -or $_.ParentProcessId -eq $b) -and $_.ExecutablePath -eq $x } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }`;
4+
const SWEEP = `$b=[int]$env:HF_CHROME_PID; $x=$env:HF_CHROME_EXE; $t=[DateTimeOffset]::FromUnixTimeMilliseconds([int64]$env:HF_CHROME_SINCE).UtcDateTime; Get-CimInstance Win32_Process | Where-Object { ($_.ProcessId -eq $b -or $_.ParentProcessId -eq $b) -and $_.ExecutablePath -eq $x -and $_.CreationDate.ToUniversalTime() -ge $t } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }`;
55

66
const WATCHDOG = `let clean = false;
77
const done = () => {
@@ -25,12 +25,20 @@ function executableOf(chrome: ChildProcess): string | undefined {
2525
}
2626

2727
/** Windows does not end a process's children with it, so Chrome's children outlive a killed CLI without this. */
28-
export function watchChromeTree(chrome: ChildProcess): ChromeWatchdog | undefined {
28+
export function watchChromeTree(
29+
chrome: ChildProcess,
30+
launchedAt: number,
31+
): ChromeWatchdog | undefined {
2932
const executable = process.platform === "win32" ? executableOf(chrome) : undefined;
3033
if (!executable || chrome.pid === undefined) return undefined;
3134
const watchdog = spawn(process.execPath, ["-e", WATCHDOG], {
3235
detached: true,
33-
env: { ...process.env, HF_CHROME_PID: String(chrome.pid), HF_CHROME_EXE: executable },
36+
env: {
37+
...process.env,
38+
HF_CHROME_PID: String(chrome.pid),
39+
HF_CHROME_EXE: executable,
40+
HF_CHROME_SINCE: String(launchedAt),
41+
},
3442
stdio: ["pipe", "ignore", "ignore"],
3543
windowsHide: true,
3644
});

0 commit comments

Comments
 (0)