import type { OxigraphBinaryLocations } from './oxigraph-binary.js'; import { type OxigraphOwnerRecordRead } from './oxigraph-owner-record.js'; import { type HolderBlock, type StoreHold } from './oxigraph-reclaim-policy.js'; import { type BootIdReader, type ProcessInstance, type ProcessLookup } from './process-probe.js'; /** * Everything the reclaim observes or does on this host; `reclaimHost` builds * it for a platform, and tests replace any part. */ export interface OrphanedOxigraphIo { /** Windows processes are not reparented, so nothing is reclaimed there. */ platform: NodeJS.Platform; lockExists(lockPath: string): Promise; /** PIDs that have the lock file open. */ listLockHolders(lockPath: string): Promise; readOwnerRecord(location: string): Promise; /** This boot's identifier, which an owner record must carry to be matched. */ bootId: BootIdReader; /** One observation of a process: running, confirmed gone, or unreadable. */ inspectProcess(pid: number): Promise; signal(pid: number, signal: NodeJS.Signals): void; sleep(ms: number): Promise; now(): number; } export interface StopOrphanedOxigraphOptions { binaryPath: string; location: string; /** * The binaries that count as this node's Oxigraph, from * `resolveOxigraphBinary`; defaults to `binaryPath` and its directory. The * recorded binary is added from the owner record. */ binaries?: OxigraphBinaryLocations; log: (message: string) => void; /** SIGTERM → SIGKILL escalation; defaults to the shared Oxigraph stop grace. */ stopGraceMs?: number; /** Upper bound on waiting for signalled orphans to release the lock. */ timeoutMs?: number; pollIntervalMs?: number; io?: Partial; } /** Runs `lsof` with these arguments; rejects like `execFile` (`code`, `killed`, `stdout`, `stderr`). */ export type LsofRunner = (args: readonly string[]) => Promise<{ stdout: string; }>; /** * The processes holding a file, from `lsof -t`. It exits 1 with no output * when no process has the file open, which is the one failure read as "no * holders". Anything else (a timeout, a missing `lsof`, a message on stderr, * output that is not a PID list) rejects, so the reclaim treats the holders * as unknown rather than absent. */ export declare function lsofLockHolderLister(run?: LsofRunner): (lockPath: string) => Promise; export declare const lsofLockHolders: (lockPath: string) => Promise; /** Linux: match open descriptors directly; `lsof` is often missing there. */ export declare function procLockHolders(lockPath: string): Promise; /** The reclaim's view of a host: its lock, owner record and process probes. */ export declare function reclaimHost(platform: NodeJS.Platform): OrphanedOxigraphIo; /** * A process instance under one PID: its start time and command line. The * start time alone can repeat within its resolution (a second for `ps`), so * a PID reused that fast is still told apart by its command. */ export interface InstanceName { start: string; command: string; } /** * What the reaper has done about the process instance holding the lock under * one PID. `instance` names it: a holder with another start time or command * under the same PID is a recycled PID and is judged afresh. */ export type Attempt = /** * Judged and left running, or its signal was refused; `instance` null: it * could not be inspected. `blocks`: why it may still be this node's * Oxigraph holding the store, or null when it is not. */ { kind: 'left'; instance: InstanceName | null; blocks: HolderBlock | null; } /** SIGTERM sent at `termAt`; SIGKILL follows once the stop grace has passed. */ | { kind: 'term-sent'; instance: InstanceName; termAt: number; } | { kind: 'kill-sent'; instance: InstanceName; }; /** What the reaper does next about one PID, after one read of it. */ export type AttemptStep = /** The instance exited (or the PID now names a process off the lock): forget it. */ { kind: 'forget'; } /** Nothing changes: a holder left running, or a signalled one still awaited. */ | { kind: 'keep'; awaited: boolean; } /** A process instance not judged yet: classify it, then `confirmStop`. */ | { kind: 'judge'; holder: ProcessInstance; } /** An unjudged holder that could not be read: report it and leave it. */ | { kind: 'leave-unreadable'; reason: string; } /** The stop grace has passed since SIGTERM: send SIGKILL. */ | { kind: 'escalate'; instance: InstanceName; }; /** * The transition for one PID from its attempt (if any) and one read of it. * `listed`: whether the PID is among the current lock holders. A signalled * holder is awaited until it is confirmed gone, listed or not (an orphan can * close LOCK before it exits, and a holder scan can fail); a read that fails * keeps it awaited but never escalates it. */ export declare function advanceAttempt(attempt: Attempt | undefined, read: ProcessLookup, at: { now: number; stopGraceMs: number; listed: boolean; }): AttemptStep; /** Whether a holder judged for stopping may be signalled, from a read taken right before. */ export type StopConfirmation = { kind: 'signal'; } /** It exited, or its PID was recycled, since it was judged. */ | { kind: 'forget'; } /** It could not be read: leave it rather than signal a process that may have changed. */ | { kind: 'leave-unconfirmed'; reason: string; }; /** Signal only the instance that was judged: a PID recycled since then has another start time or command. */ export declare function confirmStop(holder: ProcessInstance, reread: ProcessLookup): StopConfirmation; /** What one reclaim did, and whether the store is free for a new launch. */ export interface ReclaimResult { /** PIDs that were signalled. */ signalled: number[]; /** * Why the store may still be held by this node's Oxigraph, or null when it * is free: every holder was stopped or is not this node's Oxigraph for * this store. */ held: StoreHold | null; } /** * Terminate orphaned Oxigraph processes that hold this store's lock and wait * until they release it. Never throws. A caller must not launch over a store * that is still `held`: the launch would fail on the lock, and replacing the * owner record would lose the identity that lets a later reclaim stop the * holder. */ export declare function stopOrphanedOxigraph(opts: StopOrphanedOxigraphOptions): Promise; //# sourceMappingURL=oxigraph-orphan.d.ts.map