fix(daemon): warn when terminals inherit a zero RLIMIT_RTTIME (#29762)

On Linux, PTYs inherit the daemon's RLIMIT_RTTIME. When the soft or hard
limit is 0 the kernel kills timer-armed jobs (SIGXCPU / SIGKILL, exit 152
or 137) and unprivileged code cannot raise the hard limit. Read
/proc/self/limits at terminal-scope initialization and log a
rttime-limit-zero event with a remedy hint (rtkit, or the launcher /
systemd LimitRTTIME that starts Orca). No-op off Linux or when the limits
file is unreadable.

Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
Jacob Honoré
2026-10-26 07:18:52 -07:00
committed by GitHub
co-authored by Neil
parent a1c6e1273d
commit 8cfbfa5430
3 changed files with 92 additions and 0 deletions
@@ -0,0 +1,60 @@
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { warnWhenRealtimeTimeoutIsZero } from './daemon-rttime-limit'
const dirs: string[] = []
afterEach(() => {
for (const dir of dirs.splice(0)) {
rmSync(dir, { recursive: true, force: true })
}
})
function procLimits(soft: string, hard: string): string {
const dir = mkdtempSync(join(tmpdir(), 'orca-limits-'))
dirs.push(dir)
const path = join(dir, 'limits')
writeFileSync(
path,
[
'Limit Soft Limit Hard Limit Units ',
'Max realtime priority 0 0 ',
`Max realtime timeout ${soft.padEnd(21)}${hard.padEnd(21)}us `,
''
].join('\n')
)
return path
}
describe('warnWhenRealtimeTimeoutIsZero', () => {
it.each([
['0', '0'],
['0', '200000']
])('warns with a remedy hint for soft %s and hard %s', (soft, hard) => {
const log = vi.fn()
warnWhenRealtimeTimeoutIsZero(log, 'linux', procLimits(soft, hard))
expect(log).toHaveBeenCalledWith('rttime-limit-zero', {
soft,
hard,
hint: expect.stringContaining('RealtimeKit')
})
})
it.each([
['unlimited', 'unlimited'],
['200000', '200000']
])('stays quiet for soft %s and hard %s', (soft, hard) => {
const log = vi.fn()
warnWhenRealtimeTimeoutIsZero(log, 'linux', procLimits(soft, hard))
expect(log).not.toHaveBeenCalled()
})
it('stays quiet when the limits file is unreadable or off Linux', () => {
const log = vi.fn()
warnWhenRealtimeTimeoutIsZero(log, 'linux', '/definitely/missing')
warnWhenRealtimeTimeoutIsZero(log, 'darwin', procLimits('0', '0'))
warnWhenRealtimeTimeoutIsZero(log, 'win32', procLimits('0', '0'))
expect(log).not.toHaveBeenCalled()
})
})
+30
View File
@@ -0,0 +1,30 @@
import { readFileSync } from 'node:fs'
// Why: PTYs inherit the daemon's RLIMIT_RTTIME; at 0 the kernel kills timer-armed jobs (SIGXCPU soft, SIGKILL hard) and unprivileged code can't raise the hard limit (#29745).
export function warnWhenRealtimeTimeoutIsZero(
log: (event: string, details?: Record<string, unknown>) => void,
platform: NodeJS.Platform = process.platform,
limitsPath = '/proc/self/limits'
): void {
if (platform !== 'linux') {
return
}
let limits: string
try {
limits = readFileSync(limitsPath, 'utf8')
} catch {
return
}
const [, soft, hard] = /^Max realtime timeout\s+(\S+)\s+(\S+)/m.exec(limits) ?? []
if (soft !== '0' && hard !== '0') {
return
}
log('rttime-limit-zero', {
soft,
hard,
hint:
'Terminals inherit a zero RLIMIT_RTTIME, so the kernel may kill their jobs (exit 137 or 152). ' +
'Installing and enabling RealtimeKit (rtkit), then rebooting, often restores a nonzero limit; ' +
'if this keeps appearing, check the launcher, desktop session, or systemd unit (LimitRTTIME) that starts Orca.'
})
}
@@ -1,5 +1,6 @@
import { existsSync } from 'node:fs'
import { userManagerEnv } from './daemon-cgroup-scope'
import { warnWhenRealtimeTimeoutIsZero } from './daemon-rttime-limit'
import { setPtyActiveProcessLimit } from '../windows/windows-pty-job'
import { resolveTerminalScopes, type TerminalScopeRuntime } from './terminal-scopes'
import type { PtyProcessCeilingState } from '../../shared/pty-process-ceiling-state'
@@ -33,6 +34,7 @@ export function initializeTerminalScopes(
// Report requested limits even on hosts that cannot enforce them.
log('terminal-memory-limits', { platform: process.platform, ...resolved.memory })
}
warnWhenRealtimeTimeoutIsZero(log)
return resolved.ceiling
}