Files
orca/src/main/startup/gpu-fallback-marker.test.ts
T
Brennan BensonandNeil 48f0e54c03 fix(win): fall back to software rendering after repeated GPU crashes (#6857)
* fix(win): fall back to software rendering after repeated GPU crashes

On old/flaky GPU drivers the GPU child process crashes
(STATUS_BREAKPOINT / ANGLE-D3D init failure) within seconds of launch,
repeatedly — Windows crash clusters F0BDNADU79Q (Win11) and
F0BDNRZ5MDG (Win10). GPU child deaths are intentionally suppressed as
recoverable churn, so Orca never reacted and the GPU kept crashing every
launch with no self-healing path.

Track GPU child crashes in a post-launch window (3 / 30s). On the
threshold, record a gpu_fallback_engaged breadcrumb, persist a marker
file in userData, and relaunch once. The marker is read before
app.whenReady() resolves (the Store isn't available that early) so
app.disableHardwareAcceleration() + --disable-gpu take effect, then it's
consumed/cleared so the fallback applies to exactly one launch and can't
relaunch-loop. Records gpu_fallback_applied on the software-render boot.

Pure logic lives in GpuCrashFallbackTracker and the marker read/write
module, both unit-tested; the index.ts wiring is thin. Desktop only —
headless serve already uses software rendering where needed.

* fix(win): harden GPU crash fallback policy

---------

Co-authored-by: Neil <neil@stably.ai>
2026-06-30 01:21:07 -07:00

103 lines
3.6 KiB
TypeScript

import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import os from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import {
GPU_FALLBACK_MARKER_FILE,
clearGpuFallbackMarker,
readActiveGpuFallbackMarker,
readGpuFallbackMarker,
writeGpuFallbackMarker
} from './gpu-fallback-marker'
describe('gpu-fallback-marker', () => {
let userDataPath: string
const environment = {
appVersion: '1.2.3',
electronVersion: '42.3.3',
platform: 'win32' as const
}
beforeEach(() => {
userDataPath = mkdtempSync(join(os.tmpdir(), 'orca-gpu-fallback-test-'))
})
afterEach(() => {
rmSync(userDataPath, { recursive: true, force: true })
})
it('round-trips a written marker', () => {
writeGpuFallbackMarker(userDataPath, { engagedAt: 123, crashesInWindow: 3 }, environment)
expect(readGpuFallbackMarker(userDataPath)).toEqual({
schemeVersion: 2,
engagedAt: 123,
crashesInWindow: 3,
appVersion: '1.2.3',
electronVersion: '42.3.3',
platform: 'win32'
})
})
it('returns null when no marker exists', () => {
expect(readGpuFallbackMarker(userDataPath)).toBeNull()
expect(readActiveGpuFallbackMarker(userDataPath, environment)).toBeNull()
})
it('keeps an active marker for repeated launches on the same build', () => {
writeGpuFallbackMarker(userDataPath, { engagedAt: 1, crashesInWindow: 4 }, environment)
expect(existsSync(join(userDataPath, GPU_FALLBACK_MARKER_FILE))).toBe(true)
const firstRead = readActiveGpuFallbackMarker(userDataPath, environment)
expect(firstRead?.crashesInWindow).toBe(4)
expect(existsSync(join(userDataPath, GPU_FALLBACK_MARKER_FILE))).toBe(true)
const secondRead = readActiveGpuFallbackMarker(userDataPath, environment)
expect(secondRead?.crashesInWindow).toBe(4)
})
it('clears an active marker when the app build changes', () => {
writeGpuFallbackMarker(userDataPath, { engagedAt: 1, crashesInWindow: 4 }, environment)
expect(
readActiveGpuFallbackMarker(userDataPath, {
...environment,
appVersion: '1.2.4'
})
).toBeNull()
expect(existsSync(join(userDataPath, GPU_FALLBACK_MARKER_FILE))).toBe(false)
})
it('clears an active marker outside Windows', () => {
writeGpuFallbackMarker(userDataPath, { engagedAt: 1, crashesInWindow: 4 }, environment)
expect(
readActiveGpuFallbackMarker(userDataPath, {
...environment,
platform: 'linux'
})
).toBeNull()
expect(existsSync(join(userDataPath, GPU_FALLBACK_MARKER_FILE))).toBe(false)
})
it('clears a corrupt or wrong-version marker', () => {
writeFileSync(join(userDataPath, GPU_FALLBACK_MARKER_FILE), '{ not json')
expect(readGpuFallbackMarker(userDataPath)).toBeNull()
expect(readActiveGpuFallbackMarker(userDataPath, environment)).toBeNull()
expect(existsSync(join(userDataPath, GPU_FALLBACK_MARKER_FILE))).toBe(false)
writeFileSync(
join(userDataPath, GPU_FALLBACK_MARKER_FILE),
JSON.stringify({ schemeVersion: 999, engagedAt: 1, crashesInWindow: 1 })
)
expect(readGpuFallbackMarker(userDataPath)).toBeNull()
expect(readActiveGpuFallbackMarker(userDataPath, environment)).toBeNull()
expect(existsSync(join(userDataPath, GPU_FALLBACK_MARKER_FILE))).toBe(false)
})
it('can explicitly clear the marker', () => {
writeGpuFallbackMarker(userDataPath, { engagedAt: 1, crashesInWindow: 4 }, environment)
clearGpuFallbackMarker(userDataPath)
expect(readGpuFallbackMarker(userDataPath)).toBeNull()
})
})