fix(main): reset fatal-error window on backward clock jumps

This commit is contained in:
Brennan Benson
2026-07-23 16:57:08 -07:00
parent 5f79219870
commit 39e8d090c1
2 changed files with 29 additions and 1 deletions
@@ -153,6 +153,33 @@ describe('main-process fatal error guards (issue #9441)', () => {
)
})
it('reopens the window when the wall clock jumps backwards after exhaustion', async () => {
vi.resetModules()
const record = vi.fn()
vi.doMock('../crash-reporting/durable-crash-breadcrumb', () => ({
recordDurableCrashBreadcrumb: record
}))
const { recordFatalMainProcessError } = await import('./main-process-error-guards')
vi.spyOn(console, 'error').mockImplementation(() => {})
let now = 1_000_000
vi.spyOn(Date, 'now').mockImplementation(() => now)
for (let i = 0; i < 25; i++) {
recordFatalMainProcessError('main_unhandled_rejection', new Error(`storm ${i}`))
}
expect(record).toHaveBeenCalledTimes(20)
// Why: a backward jump must not trap the exhausted window and suppress every later breadcrumb.
now -= 3_600_000
recordFatalMainProcessError('main_unhandled_rejection', new Error('after backward jump'))
expect(record).toHaveBeenCalledTimes(21)
expect(record).toHaveBeenLastCalledWith(
'main_unhandled_rejection',
expect.objectContaining({ errorMessage: 'after backward jump', suppressedSinceLast: 5 }),
'main_unhandled_rejection'
)
})
it('never suppresses the fatal uncaught-exception record after a rejection storm', async () => {
vi.resetModules()
const record = vi.fn()
@@ -66,7 +66,8 @@ export function recordFatalMainProcessError(kind: FatalMainProcessErrorKind, err
// must never be lost to a window a storm already exhausted.
if (kind === 'main_unhandled_rejection') {
const now = Date.now()
if (now - recordWindowStartedAt >= RECORD_WINDOW_MS) {
// Why: a backward clock jump (sleep/resume, NTP) would otherwise trap an exhausted window and suppress every breadcrumb until wall time catches up.
if (now < recordWindowStartedAt || now - recordWindowStartedAt >= RECORD_WINDOW_MS) {
recordWindowStartedAt = now
recordWindowCount = 0
}