Files
orca/src/main/runtime/runtime-metadata.ts
T
Neil ef9e9f3fd9 perf(main): take the idle ownership poll off the main thread and batch pending marker probes (#18425)
* perf(main): take the idle ownership poll off the main thread and batch pending marker probes

The runtime-metadata ownership watch ran existsSync + readFileSync + JSON.parse on
the main thread every 10s for the life of the process. Move it to fs/promises with an
ENOENT catch (dropping the existsSync pre-check, a TOCTOU race anyway) and guard
overlapping ticks.

The base-directory poller's pending `.git` marker probes ran serially, costing
D x latency per tick for up to 300 ticks. Route them through the same
forEachWithConcurrency bound the full scan already uses.

* test(runtime): pin that a shutdown-straddling ownership read cannot republish

CodeRabbit flagged the async read resuming after stop(). The cleared
activeTransports guard already neutralizes it; this test pins that guard
rather than the interval teardown.
2026-09-03 21:16:04 -07:00

74 lines
2.5 KiB
TypeScript

import { existsSync, readFileSync, rmSync } from 'node:fs'
import { readFile } from 'node:fs/promises'
import { getRuntimeMetadataPath, type RuntimeMetadata } from '../../shared/runtime-bootstrap'
import { writeSecureJsonFile } from '../../shared/secure-file'
export function writeRuntimeMetadata(userDataPath: string, metadata: RuntimeMetadata): void {
const metadataPath = getRuntimeMetadataPath(userDataPath)
writeMetadataFile(metadataPath, metadata)
}
export function readRuntimeMetadata(userDataPath: string): RuntimeMetadata | null {
const metadataPath = getRuntimeMetadataPath(userDataPath)
if (!existsSync(metadataPath)) {
return null
}
return JSON.parse(readFileSync(metadataPath, 'utf-8')) as RuntimeMetadata
}
/** Off-thread twin of {@link readRuntimeMetadata} for pollers; a missing file is not an error. */
export async function readRuntimeMetadataAsync(
userDataPath: string
): Promise<RuntimeMetadata | null> {
let raw: string
try {
raw = await readFile(getRuntimeMetadataPath(userDataPath), 'utf-8')
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
return null
}
throw error
}
return JSON.parse(raw) as RuntimeMetadata
}
export function clearRuntimeMetadata(userDataPath: string): void {
rmSync(getRuntimeMetadataPath(userDataPath), { force: true })
}
/**
* Why: clearing metadata unconditionally on quit would race with a sibling
* Orca process during auto-updater handoff (the new process may already
* have written its own metadata before the old process finishes tearing
* down). The ownership guard — pid + runtimeId must both match the values
* the caller recorded at its own startup — keeps the clean-exit case honest
* ('not_running' instead of 'stale_bootstrap') while refusing to erase the
* replacement process's fresh bootstrap.
*
* Callers MUST capture `ownedPid` and `ownedRuntimeId` synchronously at
* startup (or at least before any shutdown await) so the comparison below
* reflects the process that actually wrote the file, not whatever state
* globals happen to hold mid-teardown.
*/
export function clearRuntimeMetadataIfOwned(
userDataPath: string,
ownedPid: number,
ownedRuntimeId: string
): void {
const current = readRuntimeMetadata(userDataPath)
if (!current) {
return
}
if (current.pid !== ownedPid) {
return
}
if (current.runtimeId !== ownedRuntimeId) {
return
}
clearRuntimeMetadata(userDataPath)
}
function writeMetadataFile(path: string, metadata: RuntimeMetadata): void {
writeSecureJsonFile(path, metadata)
}