fix(engine): a GPU probe cancelled by its caller no longer sets software mode for the process (#6541)

The GPU probe's answer is kept for the whole process and the first caller's probe is the
one every later caller waits on. When that first caller cancelled it (a prelaunched
browser dropped, a stopping check), the probe died with "This operation was aborted",
and that cancellation was kept as the answer: every browser the process launched after
it ran in software mode, with a log line blaming the GPU probe. A cancelled probe is now
forgotten and the next caller probes again.
This commit is contained in:
Miguel Ángel
2026-10-27 10:08:51 +00:00
committed by GitHub
parent 859281a60d
commit ae2c4b3942
2 changed files with 31 additions and 4 deletions
@@ -714,6 +714,21 @@ describe("resolveBrowserGpuMode", () => {
expect(close).toHaveBeenCalledOnce();
});
it("does not keep a probe its own caller cancelled as the answer for the process", async () => {
setMockWebGlProbe({ hasWebGL: true, vendor: "NVIDIA", renderer: "NVIDIA" });
const cancelled = {
launch: async () => {
throw new DOMException("This operation was aborted", "AbortError");
},
close: vi.fn(),
};
const first = resolveBrowserGpuMode("auto", { probeLifecycle: cancelled });
const second = resolveBrowserGpuMode("auto");
await expect(second).resolves.toBe("hardware");
await first;
await expect(resolveBrowserGpuMode("auto")).resolves.toBe("hardware");
});
it("passes 'software' through unchanged without probing", async () => {
const mode = await resolveBrowserGpuMode("software");
expect(mode).toBe("software");
+16 -4
View File
@@ -551,6 +551,7 @@ export const _closeBrowserAfterFailedProbeForTests = closeBrowserAfterFailedProb
interface GpuProbeOutcome {
mode: "software" | "hardware";
cause?: "no-gpu" | "probe-error";
aborted?: true;
/** Under WSL, what we checked on its GPU path (see describeWslGpuAccess). */
wslAccess?: string[];
/** Under WSL, Chrome reached the GPU only without WSLg's WAYLAND_DISPLAY. */
@@ -649,7 +650,8 @@ async function probeWebGlMode(
return resolved === "hardware" ? { mode: "hardware" } : { mode: "software", cause: "no-gpu" };
} catch (err) {
logResolvedBrowserGpuMode("software", label + formatProbeFailure(err));
return { mode: "software", cause: "probe-error" };
const aborted = err instanceof Error && err.name === "AbortError";
return { mode: "software", cause: "probe-error", ...(aborted && { aborted }) };
}
}
@@ -717,15 +719,25 @@ function wslAccess(options: GpuProbeOptions): Pick<GpuProbeOutcome, "wslAccess">
* safe failure mode; misclassifying toward hardware would error on the real
* render.
*/
async function sharedGpuProbe(options: GpuProbeOptions): Promise<GpuProbeOutcome> {
const shared = (_autoBrowserGpuModeCache ??= probeAutoBrowserGpuMode(options));
const outcome = await shared;
if (!outcome.aborted) return outcome;
if (_autoBrowserGpuModeCache === shared) _autoBrowserGpuModeCache = undefined;
const own = await probeAutoBrowserGpuMode(options);
if (!own.aborted) _autoBrowserGpuModeCache ??= Promise.resolve(own);
return own;
}
export function resolveBrowserGpuMode(
mode: EngineConfig["browserGpuMode"],
options: GpuProbeOptions = {},
): Promise<"software" | "hardware"> {
if (mode === "software") return Promise.resolve(mode);
_autoBrowserGpuModeCache ??= probeAutoBrowserGpuMode(options);
const probe = sharedGpuProbe(options);
if (mode === "auto") {
return _autoBrowserGpuModeCache.then((probed) => {
return probe.then((probed) => {
if (probed.mode === "software" && probed.wslAccess && !_wslSoftwareNoteLogged) {
_wslSoftwareNoteLogged = true;
console.error(buildWslSoftwareNote(probed));
@@ -734,7 +746,7 @@ export function resolveBrowserGpuMode(
});
}
return _autoBrowserGpuModeCache.then((probed) => {
return probe.then((probed) => {
// Warn once per cache lifetime, not once per caller: `createCaptureSession`
// resolves the mode for the probe browser AND every parallel worker, so
// an un-deduplicated warning prints N+1 times and buries itself.