From 9a2ca0c53b2e3e725ec60283a3222ce5875728a2 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Sat, 25 Jul 2026 12:59:04 -0700 Subject: [PATCH] fix(gpu): drop the in-process-gpu tier from the fallback ladder --in-process-gpu removes the GPU child entirely, but it also turns a recoverable child fault into a main-process kill. Nothing in the crash evidence says tier 2 is insufficient, so the strongest rung was risk without support. Ladder is now 1 -> 2. --- src/main/startup/gpu-fallback-marker.test.ts | 6 ++-- src/main/startup/gpu-fallback-tiers.test.ts | 31 ++++++++++---------- src/main/startup/gpu-fallback-tiers.ts | 23 +++++++-------- 3 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/main/startup/gpu-fallback-marker.test.ts b/src/main/startup/gpu-fallback-marker.test.ts index 3d7c42d9913..fcc4ff9bb80 100644 --- a/src/main/startup/gpu-fallback-marker.test.ts +++ b/src/main/startup/gpu-fallback-marker.test.ts @@ -51,8 +51,8 @@ describe('gpu-fallback-marker', () => { it('overwrites an existing marker when escalating tiers', () => { writeGpuFallbackMarker(userDataPath, { engagedAt: 1, crashesInWindow: 3, tier: 1 }, environment) - writeGpuFallbackMarker(userDataPath, { engagedAt: 2, crashesInWindow: 4, tier: 3 }, environment) - expect(readGpuFallbackMarker(userDataPath)?.tier).toBe(3) + writeGpuFallbackMarker(userDataPath, { engagedAt: 2, crashesInWindow: 4, tier: 2 }, environment) + expect(readGpuFallbackMarker(userDataPath)?.tier).toBe(2) }) it('returns null when no marker exists', () => { @@ -90,7 +90,7 @@ describe('gpu-fallback-marker', () => { platform: 'win32' }) ) - expect(readGpuFallbackMarker(userDataPath)?.tier).toBe(3) + expect(readGpuFallbackMarker(userDataPath)?.tier).toBe(2) }) it('defaults a marker with no tier to the first rung', () => { diff --git a/src/main/startup/gpu-fallback-tiers.test.ts b/src/main/startup/gpu-fallback-tiers.test.ts index 4723b1820ed..78cb5da15a6 100644 --- a/src/main/startup/gpu-fallback-tiers.test.ts +++ b/src/main/startup/gpu-fallback-tiers.test.ts @@ -11,8 +11,7 @@ describe('gpu-fallback-tiers', () => { it('escalates one rung at a time and stops at the top', () => { expect(getNextGpuFallbackTier(0)).toBe(1) expect(getNextGpuFallbackTier(1)).toBe(2) - expect(getNextGpuFallbackTier(2)).toBe(3) - expect(getNextGpuFallbackTier(3)).toBeNull() + expect(getNextGpuFallbackTier(2)).toBeNull() }) // Why: null is what bounds relaunches per build; a non-null answer here is a crash loop. @@ -35,30 +34,32 @@ describe('gpu-fallback-tiers', () => { it('identifies ladder members', () => { expect(isGpuFallbackTier(1)).toBe(true) expect(isGpuFallbackTier(0)).toBe(false) - expect(isGpuFallbackTier(4)).toBe(false) + expect(isGpuFallbackTier(3)).toBe(false) }) // Why: escalating must never drop a switch a lower tier already needed. it('is additive across tiers', () => { - const names = ([1, 2, 3] as const).map( - (tier) => new Set(getGpuFallbackTierSwitches(tier).map((entry) => entry.name)) - ) - for (const name of names[0]) { - expect(names[1].has(name)).toBe(true) - } - for (const name of names[1]) { - expect(names[2].has(name)).toBe(true) + const tier1 = new Set(getGpuFallbackTierSwitches(1).map((entry) => entry.name)) + const tier2 = new Set(getGpuFallbackTierSwitches(2).map((entry) => entry.name)) + for (const name of tier1) { + expect(tier2.has(name)).toBe(true) } }) - it('removes the GPU child process at the top tier', () => { - const tier1 = getGpuFallbackTierSwitches(1).map((entry) => entry.name) - expect(tier1).toEqual(['disable-gpu']) + it('takes the compositor off the GPU child and ANGLE off the vendor driver at tier 2', () => { + expect(getGpuFallbackTierSwitches(1).map((entry) => entry.name)).toEqual(['disable-gpu']) const tier2 = getGpuFallbackTierSwitches(2) expect(tier2.map((entry) => entry.name)).toContain('disable-gpu-compositing') expect(tier2.find((entry) => entry.name === 'use-angle')?.value).toBe('swiftshader') + }) - expect(getGpuFallbackTierSwitches(3).map((entry) => entry.name)).toContain('in-process-gpu') + // Why: in-process-gpu turns a recoverable child fault into a main-process kill. + it('never offers in-process-gpu on any rung', () => { + for (const tier of [1, 2] as const) { + expect(getGpuFallbackTierSwitches(tier).map((entry) => entry.name)).not.toContain( + 'in-process-gpu' + ) + } }) }) diff --git a/src/main/startup/gpu-fallback-tiers.ts b/src/main/startup/gpu-fallback-tiers.ts index fa1b5ddc4d9..847eba028db 100644 --- a/src/main/startup/gpu-fallback-tiers.ts +++ b/src/main/startup/gpu-fallback-tiers.ts @@ -3,20 +3,24 @@ * * `--disable-gpu` alone does not stop Chromium from spawning a GPU child for the * Viz display compositor, so a driver that CHECK-crashes at GPU init keeps - * killing every launch even with the fallback applied. Each tier removes - * strictly more of the GPU process from the path; tier 3 removes the child - * process entirely, which is the only shape a GPU-init crash cannot survive. + * killing every launch even with the fallback applied (20 of 21 crashed launches + * in the reported bundle already carried tier 1). Tier 2 takes the compositor off + * the GPU child and forces ANGLE onto SwiftShader so the vendor DLL is never loaded. + * + * The ladder deliberately stops there. `--in-process-gpu` would remove the child + * entirely, but it also turns a recoverable child fault into a main-process kill, + * and nothing in the evidence says tier 2 is insufficient. * * Tiers are additive on purpose: escalating never drops a switch that a lower * tier already needed. */ -export const GPU_FALLBACK_TIERS = [1, 2, 3] as const +export const GPU_FALLBACK_TIERS = [1, 2] as const export type GpuFallbackTier = (typeof GPU_FALLBACK_TIERS)[number] export const MIN_GPU_FALLBACK_TIER: GpuFallbackTier = 1 -export const MAX_GPU_FALLBACK_TIER: GpuFallbackTier = 3 +export const MAX_GPU_FALLBACK_TIER: GpuFallbackTier = 2 /** Tier 0 means "no fallback applied to this launch". */ export const NO_GPU_FALLBACK_TIER = 0 @@ -31,13 +35,6 @@ const TIER_SWITCHES: Record = { { name: 'disable-gpu-compositing' }, // Why: keeps a broken vendor D3D11 driver DLL out of the process even when Chromium still initializes ANGLE. { name: 'use-angle', value: 'swiftshader' } - ], - 3: [ - { name: 'disable-gpu' }, - { name: 'disable-gpu-compositing' }, - { name: 'use-angle', value: 'swiftshader' }, - // Why: last resort — no GPU child exists, so a GPU-init CHECK crash can no longer loop the app. - { name: 'in-process-gpu' } ] } @@ -63,7 +60,7 @@ export function getGpuFallbackTierSwitches(tier: GpuFallbackTier): readonly GpuF /** * Next rung above `currentTier` (0 = nothing applied yet), or null once the - * ladder is exhausted. Null is what bounds relaunches to 3 per build. + * ladder is exhausted. Null is what bounds relaunches per build. */ export function getNextGpuFallbackTier(currentTier: number): GpuFallbackTier | null { if (Number.isNaN(currentTier) || currentTier < MIN_GPU_FALLBACK_TIER) {