mirror of
https://github.com/stablyai/orca.git
synced 2026-10-27 00:02:48 +00:00
* 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>
103 lines
3.6 KiB
TypeScript
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()
|
|
})
|
|
})
|