mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 17:17:38 +00:00
fix(agent-awake): warn once while the Linux lid sleep assertion keeps failing (#24839)
* fix(agent-awake): back off and warn once when the Linux lid sleep assertion keeps failing On a headless Linux host, systemd-inhibit starts and then exits with code 1. start() reset the failure streak right after the spawn, so every 30 s retry logged a fresh warning and the retry never backed off. Keep the streak until an intentional stop or until an inhibitor has held for a full retry interval, and double the retry delay on consecutive failures (30 s up to 30 min). Fixes #20739 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(agent-awake): keep the lid assertion failure streak across idle/active cycles stop() reset the warn-once dedupe and backoff, so a host where logind denies the inhibitor still warned once per agent turn. Reset only after an inhibitor was actually held for a retry interval. * fix(agent-awake): keep the 30s lid assertion retry instead of backing off Backoff delayed re-acquiring the lid inhibitor on laptops after transient failures; warning once per failure streak already fixes the log flood. --------- Co-authored-by: brynnclaw <261708852+brynnclaw@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
co-authored by
brynnclaw
Claude Opus 5.5
OrcaWin
parent
de93f326ea
commit
0cbb973440
@@ -315,26 +315,103 @@ describe('LinuxLidSleepAssertion', () => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('logs repeated identical failures at debug until reset', () => {
|
||||
const logger = createLogger()
|
||||
const spawn = vi.fn(() => {
|
||||
throw new Error('dbus unavailable')
|
||||
})
|
||||
let now = 1_000
|
||||
const assertion = new LinuxLidSleepAssertion({
|
||||
logger,
|
||||
now: () => now,
|
||||
platform: 'linux',
|
||||
spawn
|
||||
describe('when systemd-inhibit starts but exits immediately (no usable logind)', () => {
|
||||
function createRetryingAssertion() {
|
||||
const logger = createLogger()
|
||||
const children: FakeSystemdInhibitProcess[] = []
|
||||
const spawnTimes: number[] = []
|
||||
const spawn = vi.fn(() => {
|
||||
const child = new FakeSystemdInhibitProcess()
|
||||
children.push(child)
|
||||
spawnTimes.push(Date.now())
|
||||
return child
|
||||
})
|
||||
const assertion: LinuxLidSleepAssertion = new LinuxLidSleepAssertion({
|
||||
logger,
|
||||
now: () => Date.now(),
|
||||
// Mirrors AgentAwakeService: every failure or retry tick re-runs start().
|
||||
onUnexpectedFailure: (reason) => assertion.start(reason),
|
||||
platform: 'linux',
|
||||
spawn
|
||||
})
|
||||
const failLatestChild = (): void => {
|
||||
const child = children.at(-1)
|
||||
child?.emit('exit', 1, null)
|
||||
child?.emit('close', 1, null)
|
||||
}
|
||||
const spawnGaps = (): number[] =>
|
||||
spawnTimes.slice(1).map((time, index) => time - spawnTimes[index])
|
||||
return { assertion, failLatestChild, logger, spawn, spawnGaps }
|
||||
}
|
||||
|
||||
it('warns once while it keeps retrying every interval', () => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
const { assertion, failLatestChild, logger, spawnGaps } = createRetryingAssertion()
|
||||
|
||||
assertion.start('status-change')
|
||||
for (let attempt = 0; attempt < 8; attempt++) {
|
||||
failLatestChild()
|
||||
vi.advanceTimersToNextTimer()
|
||||
}
|
||||
|
||||
expect(spawnGaps()).toEqual(Array(8).fill(LINUX_LID_SLEEP_ASSERTION_RETRY_MS))
|
||||
expect(logger.warn).toHaveBeenCalledTimes(1)
|
||||
expect(logger.debug).toHaveBeenCalledTimes(7)
|
||||
assertion.dispose()
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
assertion.start('status-change')
|
||||
now += LINUX_LID_SLEEP_ASSERTION_RETRY_MS + 1
|
||||
assertion.start('power-resume')
|
||||
assertion.stop('settings-change')
|
||||
assertion.start('status-change')
|
||||
it('stays quiet across idle/active cycles until an inhibitor is held', () => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
const { assertion, failLatestChild, logger, spawn } = createRetryingAssertion()
|
||||
|
||||
expect(logger.warn).toHaveBeenCalledTimes(2)
|
||||
expect(logger.debug).toHaveBeenCalledTimes(1)
|
||||
assertion.start('status-change')
|
||||
for (let attempt = 0; attempt < 4; attempt++) {
|
||||
failLatestChild()
|
||||
vi.advanceTimersToNextTimer()
|
||||
}
|
||||
assertion.stop('status-change')
|
||||
assertion.start('status-change')
|
||||
failLatestChild()
|
||||
|
||||
expect(spawn).toHaveBeenCalledTimes(6)
|
||||
expect(logger.warn).toHaveBeenCalledTimes(1)
|
||||
|
||||
vi.advanceTimersToNextTimer()
|
||||
vi.advanceTimersByTime(LINUX_LID_SLEEP_ASSERTION_RETRY_MS)
|
||||
assertion.stop('status-change')
|
||||
assertion.start('status-change')
|
||||
failLatestChild()
|
||||
|
||||
expect(logger.warn).toHaveBeenCalledTimes(2)
|
||||
assertion.dispose()
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
it('treats an inhibitor that held for a full retry interval as recovered', () => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
const { assertion, failLatestChild, logger } = createRetryingAssertion()
|
||||
|
||||
assertion.start('status-change')
|
||||
for (let attempt = 0; attempt < 4; attempt++) {
|
||||
failLatestChild()
|
||||
vi.advanceTimersToNextTimer()
|
||||
}
|
||||
vi.advanceTimersByTime(LINUX_LID_SLEEP_ASSERTION_RETRY_MS)
|
||||
failLatestChild()
|
||||
|
||||
expect(logger.warn).toHaveBeenCalledTimes(2)
|
||||
assertion.dispose()
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -43,6 +43,7 @@ export class LinuxLidSleepAssertion {
|
||||
private readonly platform: NodeJS.Platform
|
||||
private readonly spawn: SystemdInhibitSpawn
|
||||
private child: SystemdInhibitProcess | null = null
|
||||
private childStartedAt: number | null = null
|
||||
private retryNotBefore: number | null = null
|
||||
private retryTimer: ReturnType<typeof setTimeout> | null = null
|
||||
private systemdInhibitUnavailable = false
|
||||
@@ -94,6 +95,7 @@ export class LinuxLidSleepAssertion {
|
||||
}
|
||||
|
||||
this.child = child
|
||||
this.childStartedAt = this.now()
|
||||
const onError: SystemdInhibitErrorListener = (error) => {
|
||||
this.handleChildFailure(
|
||||
child,
|
||||
@@ -119,18 +121,19 @@ export class LinuxLidSleepAssertion {
|
||||
child.on('exit', onExit)
|
||||
child.on('close', onClose)
|
||||
child.stdin?.on('error', onError)
|
||||
// Not a held lock yet: systemd-inhibit can still exit 1 right after spawning.
|
||||
this.resetRetrySuppression()
|
||||
this.resetFailureStreak()
|
||||
}
|
||||
|
||||
stop(_reason: string): void {
|
||||
this.resetRetrySuppression()
|
||||
this.resetFailureStreak()
|
||||
if (!this.child) {
|
||||
return
|
||||
}
|
||||
const child = this.child
|
||||
this.resetFailureStreakIfHeld()
|
||||
this.child = null
|
||||
this.childStartedAt = null
|
||||
// Pipe and child errors can arrive after stop; close ends both event sources.
|
||||
this.intentionalStops.add(child)
|
||||
try {
|
||||
@@ -166,7 +169,9 @@ export class LinuxLidSleepAssertion {
|
||||
}
|
||||
this.reportedFailures.add(child)
|
||||
if (this.child === child) {
|
||||
this.resetFailureStreakIfHeld()
|
||||
this.child = null
|
||||
this.childStartedAt = null
|
||||
}
|
||||
this.handleFailure(failureKey, startReason, details, failureType)
|
||||
}
|
||||
@@ -218,9 +223,15 @@ export class LinuxLidSleepAssertion {
|
||||
this.logger.warn('[agent-awake] Linux lid sleep assertion failed', payload)
|
||||
}
|
||||
|
||||
private resetFailureStreak(): void {
|
||||
this.lastFailureKey = null
|
||||
this.warnedForLastFailure = false
|
||||
// Only a lock held for a retry interval ends the streak; idle/active cycles keep it.
|
||||
private resetFailureStreakIfHeld(): void {
|
||||
if (
|
||||
this.childStartedAt !== null &&
|
||||
this.now() - this.childStartedAt >= LINUX_LID_SLEEP_ASSERTION_RETRY_MS
|
||||
) {
|
||||
this.lastFailureKey = null
|
||||
this.warnedForLastFailure = false
|
||||
}
|
||||
}
|
||||
|
||||
private scheduleRetry(): void {
|
||||
|
||||
Reference in New Issue
Block a user