mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 09:16:55 +00:00
fix(cli): a killed browser on a busy Windows machine no longer fails check (#5607)
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { EventEmitter, once } from "node:events";
|
||||
import type { Browser } from "puppeteer-core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { closeManagedBrowser } from "./launch.js";
|
||||
|
||||
/** A browser whose close never finishes and whose process never reports 'exit', like a loaded Windows host. */
|
||||
function silentBrowser(pid: number): Browser {
|
||||
const child = Object.assign(new EventEmitter(), {
|
||||
pid,
|
||||
exitCode: null,
|
||||
signalCode: null,
|
||||
kill: () => true,
|
||||
});
|
||||
return { process: () => child, close: () => new Promise(() => {}) } as unknown as Browser;
|
||||
}
|
||||
|
||||
describe("closing a killed browser", () => {
|
||||
it("finishes once the OS reports the process gone, before Node reports its exit", async () => {
|
||||
const gone = spawn(process.execPath, ["-e", ""]);
|
||||
await once(gone, "exit");
|
||||
await expect(closeManagedBrowser(silentBrowser(gone.pid!))).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("still fails when the process is alive after the forced shutdown", async () => {
|
||||
await expect(closeManagedBrowser(silentBrowser(process.pid))).rejects.toThrow(
|
||||
"Browser process did not exit after forced shutdown.",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from "./gpuPolicy.js";
|
||||
import { normalizeErrorMessage } from "../utils/errorMessage.js";
|
||||
import { requestCliExit } from "../utils/commandResult.js";
|
||||
import { processIsAlive } from "../utils/processTree.js";
|
||||
|
||||
export class BrowserCleanupError extends AggregateError {}
|
||||
|
||||
@@ -13,6 +14,7 @@ const browsers = new Set<Browser>();
|
||||
const closing = new WeakMap<Browser, Promise<void>>();
|
||||
const pending = new Map<Promise<Browser>, AbortController>();
|
||||
const CLOSE_GRACE_MS = 1_000;
|
||||
const EXIT_POLL_MS = 50;
|
||||
let listening = false;
|
||||
let shutdown: Promise<void> | undefined;
|
||||
|
||||
@@ -125,6 +127,7 @@ async function closeBrowser(browser: Browser): Promise<void> {
|
||||
if (child.exitCode !== null || child.signalCode !== null) return;
|
||||
const exited = new Promise<void>((resolve) => child.once("exit", () => resolve()));
|
||||
let timer: NodeJS.Timeout | undefined;
|
||||
let poll: NodeJS.Timeout | undefined;
|
||||
const grace = new Promise<void>((resolve) => {
|
||||
timer = setTimeout(resolve, CLOSE_GRACE_MS);
|
||||
});
|
||||
@@ -141,8 +144,14 @@ async function closeBrowser(browser: Browser): Promise<void> {
|
||||
CLOSE_GRACE_MS,
|
||||
);
|
||||
});
|
||||
await Promise.race([exited, exitLimit]);
|
||||
const goneBeforeExitEvent = new Promise<void>((resolve) => {
|
||||
poll = setInterval(() => {
|
||||
if (child.pid === undefined || !processIsAlive(child.pid)) resolve();
|
||||
}, EXIT_POLL_MS);
|
||||
});
|
||||
await Promise.race([exited, goneBeforeExitEvent, exitLimit]);
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
clearInterval(poll);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user