diff --git a/src/main/daemon/daemon-entry.ts b/src/main/daemon/daemon-entry.ts index 70ec417a415..4dfa0f9a7c5 100644 --- a/src/main/daemon/daemon-entry.ts +++ b/src/main/daemon/daemon-entry.ts @@ -135,6 +135,7 @@ async function main(): Promise { }) let daemon: DaemonHandle | null = null + let deathWatch: MacosLoginSessionDeathWatch | null = null let shuttingDown = false // Bound the wait so a wedged native shutdown can't leave the daemon running // forever on SIGTERM/SIGINT (it would then survive a real quit, not just updates). @@ -146,6 +147,7 @@ async function main(): Promise { return } shuttingDown = true + deathWatch?.stop() daemonLog.log('shutdown', { reason }) try { if (daemon) { @@ -181,7 +183,7 @@ async function main(): Promise { return '' } } - const deathWatch = + deathWatch = loginSessionWatch && process.platform === 'darwin' ? new MacosLoginSessionDeathWatch({ probeLoginSession: e2eProbeFile @@ -242,6 +244,7 @@ async function main(): Promise { : {}), spawnSubprocess: (opts) => createPtySubprocess(opts), onIdleShutdown: () => { + deathWatch?.stop() shuttingDown = true daemonLog.log('shutdown', { reason: 'idle' }) daemonLog.close() diff --git a/src/main/daemon/macos-login-session-death-watch.test.ts b/src/main/daemon/macos-login-session-death-watch.test.ts index f4002c1865b..eebfe14a177 100644 --- a/src/main/daemon/macos-login-session-death-watch.test.ts +++ b/src/main/daemon/macos-login-session-death-watch.test.ts @@ -255,6 +255,28 @@ describe('MacosLoginSessionDeathWatch', () => { expect(clock.pendingCount()).toBe(0) }) + it('stop() prevents an in-flight resolver check from retiring the daemon', async () => { + let resolveHealth!: (health: SystemResolverHealth) => void + const resolverHealth = new Promise((resolve) => { + resolveHealth = resolve + }) + const { watch, clock, onRetire } = createWatch({ + outcomes: [ACCEPTED, REJECTED, REJECTED, REJECTED], + readResolverHealth: () => resolverHealth + }) + watch.start() + await drainMicrotasks() + await clock.advance(120_000) + await clock.advance(10_000) + await clock.advance(10_000) // retirement is now waiting on resolver health + + watch.stop() + resolveHealth('unhealthy') + await drainMicrotasks() + + expect(onRetire).not.toHaveBeenCalled() + }) + it('logs and reschedules when a probe throws instead of surfacing a rejection', async () => { const outcomes: (() => Promise)[] = [ async () => ACCEPTED,