mirror of
https://github.com/stablyai/orca.git
synced 2026-11-01 01:27:35 +00:00
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:
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user