mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 01:57:57 +00:00
Ask ps for raw terminal device numbers and name them in Orca: a verified direct lookup of /dev/ttys<minor> per distinct terminal, with an exact first-alias /dev scan fallback. Removes Apple ps's per-row /dev scan (~1 s CPU per capture on a busy Mac). Fixes #24889 Co-authored-by: Giorgio Zamparelli <giorgio.zamparelli@gmail.com> Co-authored-by: out-of-place <contact@outofplace.space>
95 lines
2.8 KiB
TypeScript
95 lines
2.8 KiB
TypeScript
import { nameDarwinTerminals } from './darwin-terminal-names'
|
|
import { errorMessage } from './error-message'
|
|
import { ProcessTableCaptureError, PS_ARGS } from './process-table-snapshot'
|
|
import { withTimeout } from './promise-timeout-fallback'
|
|
|
|
let deviceColumnUnsupported = false
|
|
let terminalNamingInFlight: Promise<string> | null = null
|
|
|
|
export async function captureDarwinProcessTable(
|
|
capture: (args: readonly string[], timeoutMs?: number) => Promise<string>,
|
|
validate: (stdout: string) => string,
|
|
budgetMs: number
|
|
): Promise<string> {
|
|
const startedAt = performance.now()
|
|
const remainingBudget = (): number => {
|
|
const remaining = Math.floor(budgetMs - (performance.now() - startedAt))
|
|
if (remaining <= 0) {
|
|
throw new ProcessTableCaptureError('capture_over_budget')
|
|
}
|
|
return remaining
|
|
}
|
|
const canonicalCapture = (): Promise<string> => capture(PS_ARGS, remainingBudget())
|
|
if (deviceColumnUnsupported || terminalNamingInFlight) {
|
|
return canonicalCapture()
|
|
}
|
|
let captured: string
|
|
try {
|
|
captured = await capture(['-axo', PS_ARGS[1].replace('tty=', 'tdev=')], remainingBudget())
|
|
} catch (error) {
|
|
if (
|
|
!(error instanceof Error) ||
|
|
!('code' in error) ||
|
|
(error.code !== 1 && error.code !== 2) ||
|
|
('killed' in error && error.killed === true) ||
|
|
!/^(?:ps: tdev: keyword not found|error: unknown user-defined format specifier "tdev")$/m.test(
|
|
errorMessage(error)
|
|
)
|
|
) {
|
|
throw error
|
|
}
|
|
deviceColumnUnsupported = true
|
|
return canonicalCapture()
|
|
}
|
|
remainingBudget()
|
|
if (terminalNamingInFlight) {
|
|
return canonicalCapture()
|
|
}
|
|
const controller = new AbortController()
|
|
const pending = nameDarwinTerminals(captured, undefined, controller.signal)
|
|
terminalNamingInFlight = pending
|
|
// Uncancelable filesystem calls keep the slot until they drain.
|
|
const clearNaming = (): void => {
|
|
if (terminalNamingInFlight === pending) {
|
|
terminalNamingInFlight = null
|
|
}
|
|
}
|
|
void pending.then(clearNaming, clearNaming)
|
|
let remainingMs: number
|
|
try {
|
|
remainingMs = remainingBudget()
|
|
} catch (error) {
|
|
controller.abort()
|
|
throw error
|
|
}
|
|
const named = await withTimeout<{ value: string } | { error: unknown } | null>(
|
|
pending.then(
|
|
(value) => ({ value }),
|
|
(error: unknown) => ({ error })
|
|
),
|
|
remainingMs,
|
|
null
|
|
)
|
|
if (named === null) {
|
|
controller.abort()
|
|
throw new ProcessTableCaptureError('capture_over_budget')
|
|
}
|
|
remainingBudget()
|
|
if ('error' in named) {
|
|
return canonicalCapture()
|
|
}
|
|
let validated: string
|
|
try {
|
|
validated = validate(named.value)
|
|
} catch {
|
|
return canonicalCapture()
|
|
}
|
|
remainingBudget()
|
|
return validated
|
|
}
|
|
|
|
export function resetDarwinProcessTableCaptureForTests(): void {
|
|
deviceColumnUnsupported = false
|
|
terminalNamingInFlight = null
|
|
}
|