mirror of
https://github.com/stablyai/orca.git
synced 2026-10-02 00:02:05 +00:00
The install-dir LPAC ACL poison (electron/electron#51761) still costs every affected machine at least one crash: the probe that detects it is setImmediate-deferred and answers 0.9-3.0s in, while createMainWindow runs synchronously in the same frame and its renderer dies at init 48-1373ms later. - Persist the poison verdict the moment the probe reports it, and await the repair (bounded at 20s) before any window is created on a launch that already carries the marker. - Do not engage the GPU safe-graphics fallback while the install-dir ACL verdict is poisoned or still outstanding. Safe graphics does not rescue a poisoned tree, and --in-process-gpu removes the GPU child, erasing the sibling-death evidence that identifies the shape (4 field reports landed in 'misc' this way). - Clear the safe-graphics marker once the repair lands, so a repaired machine stops launching software-rendered for the rest of that build. - Give the repair marker a bounded retry budget: it was written on failure and matched regardless of outcome, so one transient failure pinned a machine to 'marker-hit' for the life of that version.
191 lines
7.2 KiB
TypeScript
191 lines
7.2 KiB
TypeScript
import { app, type BrowserWindow } from 'electron'
|
|
import { relaunchApp } from '../app-relaunch'
|
|
import { destroySystemTray } from '../tray/system-tray'
|
|
import { applyGpuFallbackCommandLineSwitches } from './gpu-fallback-switches'
|
|
import {
|
|
clearGpuFallbackMarker,
|
|
readActiveGpuFallbackMarker,
|
|
writeGpuFallbackMarker,
|
|
type WindowsGpuFallbackEnvironment
|
|
} from './gpu-fallback-marker'
|
|
import {
|
|
handleGpuFallbackRecoveredLaunch,
|
|
promptForGpuFallbackRecoveredLaunch
|
|
} from '../crash-reporting/gpu-fallback-recovered-launch'
|
|
import { promptForGpuFallbackRestart } from '../crash-reporting/gpu-fallback-restart-prompt'
|
|
import { engageGpuFallbackAfterCrashBurst } from '../crash-reporting/gpu-fallback-engagement'
|
|
import { recordCrashBreadcrumb } from '../crash-reporting/crash-breadcrumb-store'
|
|
import { recordDurableCrashBreadcrumb } from '../crash-reporting/durable-crash-breadcrumb'
|
|
import { isInstallDirAclSuspect } from './windows-install-dir-acl-recovery'
|
|
import { mainProcessState as state, gpuFallbackEnvironment } from './main-process-state'
|
|
import { createGpuAccelerationAboutPanelOptions } from '../menu/gpu-acceleration-about-panel'
|
|
|
|
export function updateGpuAccelerationAboutPanel(): void {
|
|
app.setAboutPanelOptions(
|
|
createGpuAccelerationAboutPanelOptions({
|
|
appName: app.name,
|
|
appVersion: app.getVersion(),
|
|
platform: process.platform,
|
|
gpuFallbackActive: state.gpuFallbackActiveThisLaunch,
|
|
gpuFeatureStatus: state.gpuFeatureStatus
|
|
})
|
|
)
|
|
}
|
|
|
|
function getWindowsGpuFallbackEnvironment(): WindowsGpuFallbackEnvironment | null {
|
|
const environment = gpuFallbackEnvironment()
|
|
return environment.platform === 'win32' ? { ...environment, platform: 'win32' } : null
|
|
}
|
|
|
|
// Writes both crash-time and post-recovery consent states through one build-scoped path.
|
|
function persistGpuFallbackMarker(
|
|
userDataPath: string,
|
|
info: { engagedAt: number; crashesInWindow: number; userConfirmed: boolean }
|
|
): boolean {
|
|
const environment = getWindowsGpuFallbackEnvironment()
|
|
if (!environment) {
|
|
return false
|
|
}
|
|
try {
|
|
writeGpuFallbackMarker(userDataPath, info, environment)
|
|
return true
|
|
} catch (error) {
|
|
console.warn('[gpu-fallback] failed to persist marker:', error)
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Read before app.whenReady() so app.disableHardwareAcceleration() takes effect. Windows desktop only.
|
|
export function maybeApplyGpuFallbackForThisLaunch(): void {
|
|
if (state.isServeMode || process.platform !== 'win32') {
|
|
return
|
|
}
|
|
const marker = readActiveGpuFallbackMarker(app.getPath('userData'), gpuFallbackEnvironment())
|
|
if (!marker) {
|
|
return
|
|
}
|
|
state.activeGpuFallbackMarker = marker
|
|
app.disableHardwareAcceleration()
|
|
const appliedSwitches = applyGpuFallbackCommandLineSwitches(app.commandLine, process.platform)
|
|
state.gpuFallbackActiveThisLaunch = true
|
|
// Why: with no GPU child left, child-process-gone can't report a GPU fault, so
|
|
// name the applied switches in the trail any later crash report carries.
|
|
recordCrashBreadcrumb('gpu_fallback_applied', {
|
|
crashesInWindow: marker.crashesInWindow,
|
|
switches: appliedSwitches.join(',')
|
|
})
|
|
}
|
|
|
|
export async function presentGpuFallbackRecoveredLaunchPrompt(
|
|
window: BrowserWindow
|
|
): Promise<void> {
|
|
const marker = state.activeGpuFallbackMarker
|
|
if (!marker || marker.userConfirmed || window.isDestroyed() || state.isQuitting) {
|
|
return
|
|
}
|
|
// One prompt per process. A failure leaves the on-disk marker unconfirmed so the next launch retries.
|
|
state.activeGpuFallbackMarker = null
|
|
const userDataPath = app.getPath('userData')
|
|
await handleGpuFallbackRecoveredLaunch({
|
|
isQuitting: () => state.isQuitting,
|
|
prompt: () => promptForGpuFallbackRecoveredLaunch(window),
|
|
confirmSafeGraphics: () => {
|
|
persistGpuFallbackMarker(userDataPath, {
|
|
engagedAt: marker.engagedAt,
|
|
crashesInWindow: marker.crashesInWindow,
|
|
userConfirmed: true
|
|
})
|
|
},
|
|
clearSafeGraphics: () => clearGpuFallbackMarker(userDataPath),
|
|
onPromptFailed: (error) =>
|
|
console.warn('[gpu-fallback] failed to show recovered-launch prompt:', error),
|
|
onSafeGraphicsKept: () =>
|
|
recordDurableCrashBreadcrumb('gpu_fallback_safe_graphics_kept', {
|
|
crashesInWindow: marker.crashesInWindow
|
|
}),
|
|
restartWithHardware: () => {
|
|
state.isQuitting = true
|
|
relaunchApp('gpu-fallback', {
|
|
mode: 'hardware-retry',
|
|
crashesInWindow: marker.crashesInWindow
|
|
})
|
|
destroySystemTray()
|
|
app.exit(0)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Why: a burst of GPU child crashes means HW acceleration is unusable — persist a build-scoped marker and offer software rendering.
|
|
export async function handleGpuChildCrash(
|
|
reason: string,
|
|
exitCode: number | null,
|
|
crashedAt: number
|
|
): Promise<void> {
|
|
// Software rendering already active or shutting down: nothing more to do.
|
|
if (state.gpuFallbackActiveThisLaunch || state.isQuitting || state.isServeMode) {
|
|
return
|
|
}
|
|
// Why: a poisoned install DACL kills the GPU child exactly like a bad driver, but
|
|
// safe graphics does not rescue it and --in-process-gpu removes the GPU child, so
|
|
// every later crash loses the sibling deaths that identify the real cause.
|
|
if (isInstallDirAclSuspect()) {
|
|
return
|
|
}
|
|
const result = state.gpuCrashFallbackTracker.recordGpuCrash(crashedAt)
|
|
if (!result.shouldEngageFallback) {
|
|
return
|
|
}
|
|
const fallbackData = { processReason: reason, exitCode, crashesInWindow: result.crashesInWindow }
|
|
const userDataPath = app.getPath('userData')
|
|
await engageGpuFallbackAfterCrashBurst(
|
|
{ reason, exitCode, crashesInWindow: result.crashesInWindow, engagedAt: Date.now() },
|
|
{
|
|
isQuitting: () => state.isQuitting,
|
|
onEngaged: (engagement) =>
|
|
recordCrashBreadcrumb('gpu_fallback_engaged', {
|
|
reason: engagement.reason,
|
|
exitCode: engagement.exitCode,
|
|
crashesInWindow: engagement.crashesInWindow
|
|
}),
|
|
persistMarker: (engagement) =>
|
|
persistGpuFallbackMarker(userDataPath, {
|
|
engagedAt: engagement.engagedAt,
|
|
crashesInWindow: engagement.crashesInWindow,
|
|
userConfirmed: false
|
|
}),
|
|
confirmMarker: (engagement) => {
|
|
persistGpuFallbackMarker(userDataPath, {
|
|
engagedAt: engagement.engagedAt,
|
|
crashesInWindow: engagement.crashesInWindow,
|
|
userConfirmed: true
|
|
})
|
|
},
|
|
clearMarker: () => clearGpuFallbackMarker(userDataPath),
|
|
promptForRestart: () =>
|
|
promptForGpuFallbackRestart(
|
|
state.mainWindow && !state.mainWindow.isDestroyed() ? state.mainWindow : undefined
|
|
),
|
|
onPromptFailed: (error) =>
|
|
console.warn('[gpu-fallback] failed to show restart prompt:', error),
|
|
onRestartDeferred: () =>
|
|
recordDurableCrashBreadcrumb('gpu_fallback_restart_deferred', fallbackData),
|
|
restartIntoSafeGraphics: () => {
|
|
state.isQuitting = true
|
|
relaunchApp('gpu-fallback', fallbackData)
|
|
destroySystemTray()
|
|
app.exit(0)
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
export function registerGpuLifecycleHandlers(): void {
|
|
app.on('gpu-info-update', () => {
|
|
state.gpuFeatureStatus = app.getGPUFeatureStatus()
|
|
state.gpuCrashDiagnostics?.warm()
|
|
if (app.isReady()) {
|
|
updateGpuAccelerationAboutPanel()
|
|
}
|
|
})
|
|
}
|