@theford/runly 0.0.0-stage → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +157 -2
- package/dist/cli/client.d.ts +80 -0
- package/dist/cli/client.js +294 -0
- package/dist/cli/client.js.map +1 -0
- package/dist/cli/daemon-unit.d.ts +58 -0
- package/dist/cli/daemon-unit.js +289 -0
- package/dist/cli/daemon-unit.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +1482 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/install.d.ts +9 -0
- package/dist/cli/install.js +53 -0
- package/dist/cli/install.js.map +1 -0
- package/dist/cli/logs.d.ts +43 -0
- package/dist/cli/logs.js +232 -0
- package/dist/cli/logs.js.map +1 -0
- package/dist/cli/tether.d.ts +3 -0
- package/dist/cli/tether.js +65 -0
- package/dist/cli/tether.js.map +1 -0
- package/dist/cli/watchdog.d.ts +1 -0
- package/dist/cli/watchdog.js +81 -0
- package/dist/cli/watchdog.js.map +1 -0
- package/dist/core/builds.d.ts +20 -0
- package/dist/core/builds.js +93 -0
- package/dist/core/builds.js.map +1 -0
- package/dist/core/caller-env.d.ts +17 -0
- package/dist/core/caller-env.js +77 -0
- package/dist/core/caller-env.js.map +1 -0
- package/dist/core/diagnostics.d.ts +68 -0
- package/dist/core/diagnostics.js +34 -0
- package/dist/core/diagnostics.js.map +1 -0
- package/dist/core/env-vars.d.ts +35 -0
- package/dist/core/env-vars.js +32 -0
- package/dist/core/env-vars.js.map +1 -0
- package/dist/core/events.d.ts +10 -0
- package/dist/core/events.js +42 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/exec.d.ts +22 -0
- package/dist/core/exec.js +84 -0
- package/dist/core/exec.js.map +1 -0
- package/dist/core/journal.d.ts +227 -0
- package/dist/core/journal.js +395 -0
- package/dist/core/journal.js.map +1 -0
- package/dist/core/logs.d.ts +56 -0
- package/dist/core/logs.js +259 -0
- package/dist/core/logs.js.map +1 -0
- package/dist/core/manifest.d.ts +292 -0
- package/dist/core/manifest.js +179 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/core/namespaces.d.ts +7 -0
- package/dist/core/namespaces.js +59 -0
- package/dist/core/namespaces.js.map +1 -0
- package/dist/core/paths.d.ts +31 -0
- package/dist/core/paths.js +65 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/policy.d.ts +94 -0
- package/dist/core/policy.js +122 -0
- package/dist/core/policy.js.map +1 -0
- package/dist/core/ports.d.ts +52 -0
- package/dist/core/ports.js +94 -0
- package/dist/core/ports.js.map +1 -0
- package/dist/core/presets.d.ts +25 -0
- package/dist/core/presets.js +86 -0
- package/dist/core/presets.js.map +1 -0
- package/dist/core/procscan.d.ts +111 -0
- package/dist/core/procscan.js +342 -0
- package/dist/core/procscan.js.map +1 -0
- package/dist/core/retention.d.ts +97 -0
- package/dist/core/retention.js +322 -0
- package/dist/core/retention.js.map +1 -0
- package/dist/core/sleep.d.ts +51 -0
- package/dist/core/sleep.js +99 -0
- package/dist/core/sleep.js.map +1 -0
- package/dist/core/tree-ledger.d.ts +39 -0
- package/dist/core/tree-ledger.js +92 -0
- package/dist/core/tree-ledger.js.map +1 -0
- package/dist/core/types.d.ts +21 -0
- package/dist/core/types.js +6 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/units.d.ts +20 -0
- package/dist/core/units.js +69 -0
- package/dist/core/units.js.map +1 -0
- package/dist/core/upkeep.d.ts +90 -0
- package/dist/core/upkeep.js +176 -0
- package/dist/core/upkeep.js.map +1 -0
- package/dist/core/util.d.ts +46 -0
- package/dist/core/util.js +168 -0
- package/dist/core/util.js.map +1 -0
- package/dist/core/version.d.ts +39 -0
- package/dist/core/version.js +144 -0
- package/dist/core/version.js.map +1 -0
- package/dist/core/worktree.d.ts +51 -0
- package/dist/core/worktree.js +309 -0
- package/dist/core/worktree.js.map +1 -0
- package/dist/daemon/budget.d.ts +155 -0
- package/dist/daemon/budget.js +288 -0
- package/dist/daemon/budget.js.map +1 -0
- package/dist/daemon/election.d.ts +9 -0
- package/dist/daemon/election.js +140 -0
- package/dist/daemon/election.js.map +1 -0
- package/dist/daemon/engine.d.ts +1703 -0
- package/dist/daemon/engine.js +5305 -0
- package/dist/daemon/engine.js.map +1 -0
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.js +412 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/proxy.d.ts +108 -0
- package/dist/daemon/proxy.js +615 -0
- package/dist/daemon/proxy.js.map +1 -0
- package/dist/daemon/supervisor.d.ts +101 -0
- package/dist/daemon/supervisor.js +493 -0
- package/dist/daemon/supervisor.js.map +1 -0
- package/dist/drivers/appliances.d.ts +11 -0
- package/dist/drivers/appliances.js +174 -0
- package/dist/drivers/appliances.js.map +1 -0
- package/dist/drivers/datastores.d.ts +144 -0
- package/dist/drivers/datastores.js +775 -0
- package/dist/drivers/datastores.js.map +1 -0
- package/dist/drivers/preview.d.ts +69 -0
- package/dist/drivers/preview.js +605 -0
- package/dist/drivers/preview.js.map +1 -0
- package/dist/drivers/types.d.ts +70 -0
- package/dist/drivers/types.js +13 -0
- package/dist/drivers/types.js.map +1 -0
- package/package.json +63 -4
- package/schema/backlot.schema.json +549 -0
- package/schema/runly.schema.json +556 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { ServiceSpec } from '../core/manifest.js';
|
|
2
|
+
import type { ServicePid } from '../core/types.js';
|
|
3
|
+
export interface ServiceEvent {
|
|
4
|
+
at: number;
|
|
5
|
+
service: string;
|
|
6
|
+
event: string;
|
|
7
|
+
}
|
|
8
|
+
/** How a service that runly gave up on last ended (decision 0039). */
|
|
9
|
+
export interface ServiceExit {
|
|
10
|
+
/** Exit code of the last process, null when a signal ended it or it never spawned. */
|
|
11
|
+
code: number | null;
|
|
12
|
+
signal: string | null;
|
|
13
|
+
/** `crash-loop`, `daemonized`, `spawn-failed`, or `boot-failed` (it never became ready in a bind, 0.18.1). */
|
|
14
|
+
reason: 'crash-loop' | 'daemonized' | 'spawn-failed' | 'boot-failed';
|
|
15
|
+
/** For `boot-failed`: what the readiness check said. */
|
|
16
|
+
detail?: string;
|
|
17
|
+
}
|
|
18
|
+
/** What the engine learns about a service between starts (decision 0035). */
|
|
19
|
+
export interface SupervisorHooks {
|
|
20
|
+
/** It exited on its own and a restart is pending: hold its port's connections. */
|
|
21
|
+
onCrashed?: (service: string) => void;
|
|
22
|
+
/** The pending restart launched a new process: probe it and forward again. */
|
|
23
|
+
onRelaunched?: (service: string) => void;
|
|
24
|
+
/** It gave up restarting (flapping, or a daemonized service). */
|
|
25
|
+
onGaveUp?: (service: string) => void;
|
|
26
|
+
}
|
|
27
|
+
export declare class EnvSupervisor {
|
|
28
|
+
readonly envId: string;
|
|
29
|
+
/** The caller's worktree — services run in place there (decision 0032). */
|
|
30
|
+
private readonly root;
|
|
31
|
+
private readonly logDir;
|
|
32
|
+
/** Fired when a service flaps past its restart budget (decision 0007/0010), with how it last ended. */
|
|
33
|
+
private readonly onDegraded?;
|
|
34
|
+
/** Fired whenever a service's pid changes (start/restart) so the journal
|
|
35
|
+
* stays truthful for recovery — a stale pid gets an innocent SIGTERM and
|
|
36
|
+
* misses the real orphan holding the port. */
|
|
37
|
+
private readonly onPidsChanged?;
|
|
38
|
+
/** Fired after a service was stopped on purpose — the proxy stops forwarding to it (decision 0033). */
|
|
39
|
+
private readonly onStopped?;
|
|
40
|
+
private readonly hooks;
|
|
41
|
+
private services;
|
|
42
|
+
readonly events: ServiceEvent[];
|
|
43
|
+
constructor(envId: string,
|
|
44
|
+
/** The caller's worktree — services run in place there (decision 0032). */
|
|
45
|
+
root: string, logDir: string,
|
|
46
|
+
/** Fired when a service flaps past its restart budget (decision 0007/0010), with how it last ended. */
|
|
47
|
+
onDegraded?: ((service: string, exit: ServiceExit) => void) | undefined,
|
|
48
|
+
/** Fired whenever a service's pid changes (start/restart) so the journal
|
|
49
|
+
* stays truthful for recovery — a stale pid gets an innocent SIGTERM and
|
|
50
|
+
* misses the real orphan holding the port. */
|
|
51
|
+
onPidsChanged?: (() => void) | undefined,
|
|
52
|
+
/** Fired after a service was stopped on purpose — the proxy stops forwarding to it (decision 0033). */
|
|
53
|
+
onStopped?: ((service: string) => void) | undefined, hooks?: SupervisorHooks);
|
|
54
|
+
private note;
|
|
55
|
+
logPath(name: string): string;
|
|
56
|
+
logs(name: string): string;
|
|
57
|
+
pids(): Record<string, ServicePid>;
|
|
58
|
+
/** The exit code of a service's last process; null while it runs, or when a signal ended it. */
|
|
59
|
+
exitCodeOf(name: string): number | null;
|
|
60
|
+
allHealthyPids(): boolean;
|
|
61
|
+
start(name: string, spec: ServiceSpec, env: NodeJS.ProcessEnv, secrets?: string[]): void;
|
|
62
|
+
waitReady(name: string, spec: ServiceSpec, url: string | undefined, env: NodeJS.ProcessEnv): Promise<void>;
|
|
63
|
+
/** Stop every service; returns any that refused to die (empty is the norm). */
|
|
64
|
+
stopAll(): Promise<Record<string, ServicePid>>;
|
|
65
|
+
/**
|
|
66
|
+
* Stop just these services (an `up` restarting only the ones whose build
|
|
67
|
+
* output changed, decision 0032); the rest keep running. Same contract as
|
|
68
|
+
* stopAll: survivors are returned so the caller can reap and record them.
|
|
69
|
+
*/
|
|
70
|
+
stopSome(names: string[]): Promise<Record<string, ServicePid>>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* SIGTERM a group, wait for it to actually die, then SIGKILL and confirm.
|
|
74
|
+
*
|
|
75
|
+
* The previous recovery path fired one SIGTERM and moved on, which anything
|
|
76
|
+
* with a graceful-shutdown handler (a .NET host, `ng serve` under a trapping
|
|
77
|
+
* shell) simply outlives — and since the caller then dropped the pid, the
|
|
78
|
+
* survivor became unreachable forever. Escalation plus a verified verdict is
|
|
79
|
+
* what makes "reaped" mean reaped.
|
|
80
|
+
*
|
|
81
|
+
* Returns true only when the observed and supplied recorded groups are empty.
|
|
82
|
+
* `observeGroup` hands the current group to reapPids before signalling so its
|
|
83
|
+
* ownership survives even if the recorded process exits during reclamation.
|
|
84
|
+
*/
|
|
85
|
+
export declare function killGroupVerified(pid: number, recordedStart?: number, graceMs?: number, recordedGroup?: number, observeGroup?: (pgid: number) => void): Promise<boolean>;
|
|
86
|
+
/**
|
|
87
|
+
* SIGTERM one process, then SIGKILL it after `graceMs` — never its group, and
|
|
88
|
+
* only while it is still the process recorded with `recordedStart`. For a
|
|
89
|
+
* process that shares a group with something runly does not own (`runly db
|
|
90
|
+
* with`'s command at a terminal, decision 0039). True once it is gone.
|
|
91
|
+
*/
|
|
92
|
+
export declare function killPidVerified(pid: number, recordedStart?: number, graceMs?: number): Promise<boolean>;
|
|
93
|
+
/**
|
|
94
|
+
* Kill PIDs recorded by a previous daemon life (recovery, decision 0009).
|
|
95
|
+
*
|
|
96
|
+
* Returns the entries that were NOT confirmed dead. The caller must keep those
|
|
97
|
+
* in the journal — a forgotten pid is an orphan nobody can ever reclaim.
|
|
98
|
+
*/
|
|
99
|
+
export declare function reapPids(pids: Record<string, ServicePid>, kill?: typeof killGroupVerified): Promise<Record<string, ServicePid>>;
|
|
100
|
+
export declare function serviceGroups(rec: ServicePid): number[];
|
|
101
|
+
export declare function mergeServicePids(...sets: Array<Record<string, ServicePid> | undefined>): Record<string, ServicePid>;
|
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon-as-parent service supervision (decision 0009/0010): spawn with env
|
|
3
|
+
* injection, capture logs, probe readiness (http/log/cmd) with fatal-log
|
|
4
|
+
* fast-fail, restart session services with bounded backoff, and never let a
|
|
5
|
+
* crash pass silently.
|
|
6
|
+
*/
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
9
|
+
import { mkdirSync } from 'node:fs';
|
|
10
|
+
import { BrokerError, environmentHint, now, safeJoin } from '../core/util.js';
|
|
11
|
+
import { runBounded } from '../core/exec.js';
|
|
12
|
+
import { redactStream } from '../core/caller-env.js';
|
|
13
|
+
import { stateRoot } from '../core/paths.js';
|
|
14
|
+
import { groupAlive, processGroup, sameProcess, serviceTag, startTime } from '../core/procscan.js';
|
|
15
|
+
import { LogWriter, logFileOf, startMarker } from '../core/logs.js';
|
|
16
|
+
/** Uptime past which a crash counts as fresh rather than part of a flap. */
|
|
17
|
+
const STABLE_MS = () => Number(process.env.BACKLOT_STABLE_MS ?? 60_000);
|
|
18
|
+
export class EnvSupervisor {
|
|
19
|
+
envId;
|
|
20
|
+
root;
|
|
21
|
+
logDir;
|
|
22
|
+
onDegraded;
|
|
23
|
+
onPidsChanged;
|
|
24
|
+
onStopped;
|
|
25
|
+
hooks;
|
|
26
|
+
services = new Map();
|
|
27
|
+
events = [];
|
|
28
|
+
constructor(envId,
|
|
29
|
+
/** The caller's worktree — services run in place there (decision 0032). */
|
|
30
|
+
root, logDir,
|
|
31
|
+
/** Fired when a service flaps past its restart budget (decision 0007/0010), with how it last ended. */
|
|
32
|
+
onDegraded,
|
|
33
|
+
/** Fired whenever a service's pid changes (start/restart) so the journal
|
|
34
|
+
* stays truthful for recovery — a stale pid gets an innocent SIGTERM and
|
|
35
|
+
* misses the real orphan holding the port. */
|
|
36
|
+
onPidsChanged,
|
|
37
|
+
/** Fired after a service was stopped on purpose — the proxy stops forwarding to it (decision 0033). */
|
|
38
|
+
onStopped, hooks = {}) {
|
|
39
|
+
this.envId = envId;
|
|
40
|
+
this.root = root;
|
|
41
|
+
this.logDir = logDir;
|
|
42
|
+
this.onDegraded = onDegraded;
|
|
43
|
+
this.onPidsChanged = onPidsChanged;
|
|
44
|
+
this.onStopped = onStopped;
|
|
45
|
+
this.hooks = hooks;
|
|
46
|
+
mkdirSync(logDir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
note(service, event) {
|
|
49
|
+
this.events.push({ at: Date.now(), service, event });
|
|
50
|
+
if (this.events.length > 100)
|
|
51
|
+
this.events.shift();
|
|
52
|
+
}
|
|
53
|
+
logPath(name) {
|
|
54
|
+
return logFileOf(this.logDir, name);
|
|
55
|
+
}
|
|
56
|
+
logs(name) {
|
|
57
|
+
return this.services.get(name)?.buf ?? '';
|
|
58
|
+
}
|
|
59
|
+
pids() {
|
|
60
|
+
const out = {};
|
|
61
|
+
for (const [name, r] of this.services) {
|
|
62
|
+
if (r.proc?.pid && r.proc.exitCode === null)
|
|
63
|
+
out[name] = { pid: r.proc.pid, startTime: r.startTime };
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
/** The exit code of a service's last process; null while it runs, or when a signal ended it. */
|
|
68
|
+
exitCodeOf(name) {
|
|
69
|
+
return this.services.get(name)?.proc?.exitCode ?? null;
|
|
70
|
+
}
|
|
71
|
+
allHealthyPids() {
|
|
72
|
+
return [...this.services.values()].every((r) => r.proc.exitCode === null);
|
|
73
|
+
}
|
|
74
|
+
start(name, spec, env, secrets = []) {
|
|
75
|
+
// A start while the previous incarnation waits out a crash backoff (it is
|
|
76
|
+
// missing from pids(), so a wake or an `up` starts it again): the pending
|
|
77
|
+
// relaunch is cancelled first. Left armed, it spawned a second process
|
|
78
|
+
// the map no longer named — untracked, and squatting the internal port.
|
|
79
|
+
const previous = this.services.get(name);
|
|
80
|
+
if (previous) {
|
|
81
|
+
previous.expectedExit = true;
|
|
82
|
+
if (previous.restartTimer) {
|
|
83
|
+
clearTimeout(previous.restartTimer);
|
|
84
|
+
previous.restartTimer = null;
|
|
85
|
+
}
|
|
86
|
+
if (previous.proc?.pid && previous.proc.exitCode === null && previous.proc.signalCode === null) {
|
|
87
|
+
// Still running (a caller that should have stopped it first): its
|
|
88
|
+
// whole group goes, rather than run on beside the new one.
|
|
89
|
+
try {
|
|
90
|
+
process.kill(-previous.proc.pid, 'SIGKILL');
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
/* already gone */
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
this.note(name, 'replaced by a new start');
|
|
97
|
+
}
|
|
98
|
+
const cmd = spec.run;
|
|
99
|
+
// A repo can already run arbitrary shell here, so this is not a privilege
|
|
100
|
+
// boundary — it makes an ACCIDENT loud. `cwd: ../sibling` silently ran the
|
|
101
|
+
// service outside the worktree it is bound to, against files runly never
|
|
102
|
+
// fingerprinted.
|
|
103
|
+
const cwd = spec.cwd ? safeJoin(this.root, spec.cwd, `service '${name}' cwd`) : this.root;
|
|
104
|
+
const running = { proc: null, buf: '', probeBuf: '', restarts: 0, expectedExit: false, restartTimer: null, startedAt: now(), log: new LogWriter(this.logPath(name)) };
|
|
105
|
+
let launches = 0;
|
|
106
|
+
const launch = () => {
|
|
107
|
+
running.restartTimer = null;
|
|
108
|
+
// A teardown that landed while this restart was pending: do not respawn.
|
|
109
|
+
if (running.expectedExit)
|
|
110
|
+
return;
|
|
111
|
+
launches++;
|
|
112
|
+
const proc = spawn('sh', ['-c', cmd], {
|
|
113
|
+
cwd,
|
|
114
|
+
// The tag rides in the environment so it is INHERITED by every
|
|
115
|
+
// descendant. After an ungraceful death the sh -c wrapper is gone but
|
|
116
|
+
// the real server still carries it, which is what lets a later daemon
|
|
117
|
+
// find and reclaim an orphan it never spawned itself (issue #5).
|
|
118
|
+
env: { ...process.env, ...env, ...serviceTag(this.envId, name, stateRoot()) },
|
|
119
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
120
|
+
// Own process group, so signals reach the service and not just the
|
|
121
|
+
// sh -c wrapper. On Linux (dash) the wrapper forks instead of
|
|
122
|
+
// exec-ing, so killing only proc.pid orphaned the actual server —
|
|
123
|
+
// it kept the port and every rebind-to-same-port flow failed with
|
|
124
|
+
// "occupied by a foreign process" (BACKLOG P1, 2026-07-11).
|
|
125
|
+
// Services still survive a daemon crash (crash-recovery contract):
|
|
126
|
+
// nothing signals the group when the daemon itself dies.
|
|
127
|
+
detached: true,
|
|
128
|
+
});
|
|
129
|
+
running.proc = proc;
|
|
130
|
+
running.startedAt = now();
|
|
131
|
+
// Capture identity immediately: once the pid exits this is unreadable,
|
|
132
|
+
// and an un-pinned pid is one the reaper must refuse to signal.
|
|
133
|
+
running.startTime = proc.pid ? startTime(proc.pid) : undefined;
|
|
134
|
+
// `--since up` reads from the last of these (decision 0038).
|
|
135
|
+
running.log.line(startMarker(name, proc.pid, launches > 1 ? `restart ${launches - 1} after it exited` : ''));
|
|
136
|
+
this.onPidsChanged?.();
|
|
137
|
+
if (launches > 1)
|
|
138
|
+
this.hooks.onRelaunched?.(name);
|
|
139
|
+
for (const [label, stream] of [['out', proc.stdout], ['err', proc.stderr]]) {
|
|
140
|
+
const decoder = new StringDecoder('utf8');
|
|
141
|
+
const redact = redactStream(secrets);
|
|
142
|
+
const sink = (s) => {
|
|
143
|
+
running.buf = (running.buf + s).slice(-64_000);
|
|
144
|
+
running.log.write(label, s);
|
|
145
|
+
};
|
|
146
|
+
stream.on('data', (d) => {
|
|
147
|
+
const text = decoder.write(d);
|
|
148
|
+
running.probeBuf = (running.probeBuf + text).slice(-64_000);
|
|
149
|
+
sink(redact(text));
|
|
150
|
+
});
|
|
151
|
+
stream.on('end', () => {
|
|
152
|
+
sink(redact(decoder.end(), true));
|
|
153
|
+
running.log.end(label);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
// A spawn failure (EAGAIN/EMFILE under fleet load) emits 'error'; with no
|
|
157
|
+
// listener it becomes an uncaught exception that kills the whole daemon.
|
|
158
|
+
proc.on('error', (err) => {
|
|
159
|
+
this.note(name, `spawn error: ${err.message}`);
|
|
160
|
+
// A spawn failure emits 'error' with NO 'exit', so the restart logic
|
|
161
|
+
// below never ran: supervision simply stopped while the environment
|
|
162
|
+
// stayed 'hot' and reported healthy. Drive the same path as an exit.
|
|
163
|
+
if (running.expectedExit || running.spawnFailed)
|
|
164
|
+
return;
|
|
165
|
+
running.spawnFailed = true;
|
|
166
|
+
this.onPidsChanged?.();
|
|
167
|
+
if (running.restarts < 3) {
|
|
168
|
+
running.restarts++;
|
|
169
|
+
this.hooks.onCrashed?.(name);
|
|
170
|
+
this.note(name, `restarting after spawn failure (attempt ${running.restarts})`);
|
|
171
|
+
running.restartTimer = setTimeout(() => {
|
|
172
|
+
running.spawnFailed = false;
|
|
173
|
+
launch();
|
|
174
|
+
}, 500 * running.restarts);
|
|
175
|
+
running.restartTimer.unref();
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
this.note(name, 'spawn keeps failing — giving up');
|
|
179
|
+
this.hooks.onGaveUp?.(name);
|
|
180
|
+
this.onDegraded?.(name, { code: null, signal: null, reason: 'spawn-failed' });
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
proc.on('exit', (code, signal) => {
|
|
184
|
+
if (running.expectedExit)
|
|
185
|
+
return;
|
|
186
|
+
// A service that DAEMONIZES (forks and returns 0 immediately) is not a
|
|
187
|
+
// supervised service: runly restarts it, each restart forks another
|
|
188
|
+
// background copy, and those copies escape both the group kill and, if
|
|
189
|
+
// they detach far enough, the tag scan. Refuse it rather than
|
|
190
|
+
// multiplying processes nobody can reclaim.
|
|
191
|
+
if (code === 0 && now() - running.startedAt < 2000 && running.restarts === 0) {
|
|
192
|
+
this.note(name, 'exited 0 immediately — a service must stay in the FOREGROUND (it looks daemonized)');
|
|
193
|
+
running.expectedExit = true;
|
|
194
|
+
this.hooks.onGaveUp?.(name);
|
|
195
|
+
this.onDegraded?.(name, { code, signal, reason: 'daemonized' });
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
this.note(name, signal ? `exited (signal ${signal})` : `exited (${code})`);
|
|
199
|
+
this.onPidsChanged?.(); // the dead pid must leave the journal
|
|
200
|
+
// The budget is for FLAPPING — a tight crash loop — not for a service's
|
|
201
|
+
// whole lifetime. Without this reset, three unrelated crashes hours
|
|
202
|
+
// apart degraded a long-lived environment that was never unhealthy.
|
|
203
|
+
if (now() - running.startedAt > STABLE_MS())
|
|
204
|
+
running.restarts = 0;
|
|
205
|
+
// Bounded restart for long-lived services (decision 0010).
|
|
206
|
+
if (running.restarts < 3) {
|
|
207
|
+
running.restarts++;
|
|
208
|
+
// Hold, don't refuse (decision 0035): a client arriving between the
|
|
209
|
+
// crash and the relaunch waits on the public port.
|
|
210
|
+
this.hooks.onCrashed?.(name);
|
|
211
|
+
this.note(name, `restarting (attempt ${running.restarts})`);
|
|
212
|
+
running.restartTimer = setTimeout(launch, 500 * running.restarts);
|
|
213
|
+
running.restartTimer.unref();
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
this.note(name, 'flapping — giving up');
|
|
217
|
+
this.hooks.onGaveUp?.(name);
|
|
218
|
+
this.onDegraded?.(name, { code, signal, reason: 'crash-loop' });
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
launch();
|
|
223
|
+
this.services.set(name, running);
|
|
224
|
+
this.note(name, 'started');
|
|
225
|
+
}
|
|
226
|
+
async waitReady(name, spec, url, env) {
|
|
227
|
+
const ready = spec.ready ?? (spec.port ? { http: '/' } : { log: '.' });
|
|
228
|
+
const timeoutMs = (ready.timeout ?? 120) * 1000;
|
|
229
|
+
const fatal = spec.fatal_logs ? new RegExp(spec.fatal_logs) : undefined;
|
|
230
|
+
const start = Date.now();
|
|
231
|
+
const running = this.services.get(name);
|
|
232
|
+
if (!running)
|
|
233
|
+
throw new BrokerError('env-error', `service '${name}' was never started`, name);
|
|
234
|
+
for (;;) {
|
|
235
|
+
const buf = running.buf;
|
|
236
|
+
if (fatal && fatal.test(running.probeBuf)) {
|
|
237
|
+
throw new BrokerError('work-error', `service '${name}' hit a fatal log marker during boot`, name, tail(buf));
|
|
238
|
+
}
|
|
239
|
+
// The daemonization detector (exit 0 in <2s) already gave up on this
|
|
240
|
+
// service — polling its dead probe for the rest of ready.timeout only
|
|
241
|
+
// delayed the verdict and then blamed the ENVIRONMENT for the repo's
|
|
242
|
+
// own backgrounding run command.
|
|
243
|
+
if (running.expectedExit && running.proc.exitCode === 0 && running.restarts === 0) {
|
|
244
|
+
throw new BrokerError('work-error', `service '${name}' exited 0 immediately — a service must stay in the FOREGROUND (it looks daemonized)`, name, tail(buf));
|
|
245
|
+
}
|
|
246
|
+
if (running.proc.exitCode !== null && running.restarts >= 3) {
|
|
247
|
+
// Still the service's own failure (decision 0039 marks it failed and
|
|
248
|
+
// keeps the rest running); a missing tool or runtime says so.
|
|
249
|
+
throw new BrokerError('work-error', `service '${name}' exited during boot (${running.proc.exitCode})${environmentHint(tail(buf)) ?? ''}`, name, tail(buf));
|
|
250
|
+
}
|
|
251
|
+
if (ready.http && url) {
|
|
252
|
+
// Probe every address the advertised host can mean. `localhost` is
|
|
253
|
+
// ambiguous on a dual-stack machine: macOS resolves it to ::1 first,
|
|
254
|
+
// and a service bound IPv4-only (the common case — see
|
|
255
|
+
// examples/hello-python) is simply not there. The readiness probe then
|
|
256
|
+
// times out against a service that IS running and healthy.
|
|
257
|
+
for (const candidate of probeUrls(url)) {
|
|
258
|
+
try {
|
|
259
|
+
const res = await fetch(`${candidate}${ready.http}`, { signal: AbortSignal.timeout(1500) });
|
|
260
|
+
if (res.status === 200)
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
/* not on this address — try the next */
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else if (ready.log) {
|
|
269
|
+
if (new RegExp(ready.log).test(running.probeBuf))
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
else if (ready.cmd) {
|
|
273
|
+
// Bounded by what remains of the readiness budget: a probe command
|
|
274
|
+
// that itself hangs used to block this loop long past ready.timeout.
|
|
275
|
+
const remainingS = Math.max(1, Math.ceil((timeoutMs - (Date.now() - start)) / 1000));
|
|
276
|
+
const r = await runBounded(ready.cmd, this.root, remainingS, { ...process.env, ...env });
|
|
277
|
+
if (r.code === 0 && !r.timedOut)
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (Date.now() - start > timeoutMs) {
|
|
281
|
+
throw new BrokerError('env-error', `service '${name}' not ready after ${ready.timeout ?? 120}s`, name, tail(running.buf));
|
|
282
|
+
}
|
|
283
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/** Stop every service; returns any that refused to die (empty is the norm). */
|
|
287
|
+
async stopAll() {
|
|
288
|
+
return this.stopSome([...this.services.keys()]);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Stop just these services (an `up` restarting only the ones whose build
|
|
292
|
+
* output changed, decision 0032); the rest keep running. Same contract as
|
|
293
|
+
* stopAll: survivors are returned so the caller can reap and record them.
|
|
294
|
+
*/
|
|
295
|
+
async stopSome(names) {
|
|
296
|
+
const survivors = {};
|
|
297
|
+
for (const name of names) {
|
|
298
|
+
const r = this.services.get(name);
|
|
299
|
+
if (!r)
|
|
300
|
+
continue;
|
|
301
|
+
r.expectedExit = true;
|
|
302
|
+
// Cancel any pending restart BEFORE it can fire — otherwise launch()
|
|
303
|
+
// respawns an untracked process that squats the port after teardown.
|
|
304
|
+
if (r.restartTimer) {
|
|
305
|
+
clearTimeout(r.restartTimer);
|
|
306
|
+
r.restartTimer = null;
|
|
307
|
+
}
|
|
308
|
+
// NOTE the exitCode check is deliberately absent: `sh -c` forks, so the
|
|
309
|
+
// wrapper can be gone while the real server lives on in its group. The
|
|
310
|
+
// identity check inside killGroupVerified fails safe for a dead leader, so
|
|
311
|
+
// survivors get RECORDED for tag reclaim instead of vanishing silently.
|
|
312
|
+
if (r.proc && r.proc.pid) {
|
|
313
|
+
// Verify the group is actually gone rather than resolving as soon as
|
|
314
|
+
// SIGKILL is *sent* — the caller goes on to delete the env root and
|
|
315
|
+
// drop the pid, so a survivor here becomes an untrackable orphan.
|
|
316
|
+
const remaining = await reapPids({ [name]: { pid: r.proc.pid, startTime: r.startTime } });
|
|
317
|
+
if (remaining[name]) {
|
|
318
|
+
this.note(name, `still running after SIGKILL (pid ${r.proc.pid})`);
|
|
319
|
+
survivors[name] = remaining[name];
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
this.note(name, survivors[name] ? 'stop failed' : 'stopped');
|
|
323
|
+
this.services.delete(name);
|
|
324
|
+
this.onStopped?.(name);
|
|
325
|
+
}
|
|
326
|
+
return survivors;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* The addresses an advertised URL may actually be served on.
|
|
331
|
+
*
|
|
332
|
+
* Keeps the CONSUMER-facing url unchanged (that is a contract) while making the
|
|
333
|
+
* internal readiness probe robust to IPv4/IPv6 resolution order.
|
|
334
|
+
*/
|
|
335
|
+
function probeUrls(url) {
|
|
336
|
+
if (!url.includes('//localhost'))
|
|
337
|
+
return [url];
|
|
338
|
+
return [url, url.replace('//localhost', '//127.0.0.1')];
|
|
339
|
+
}
|
|
340
|
+
const tail = (s, n = 600) => s.slice(-n);
|
|
341
|
+
/** Signal a whole process group, falling back to the single pid we know of. */
|
|
342
|
+
function signalGroup(pgid, pid, signal) {
|
|
343
|
+
try {
|
|
344
|
+
process.kill(-pgid, signal);
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
// Not a group leader (or the group is already gone) — at minimum reach the
|
|
348
|
+
// one process we can name.
|
|
349
|
+
try {
|
|
350
|
+
process.kill(pid, signal);
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
/* already gone */
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* SIGTERM a group, wait for it to actually die, then SIGKILL and confirm.
|
|
359
|
+
*
|
|
360
|
+
* The previous recovery path fired one SIGTERM and moved on, which anything
|
|
361
|
+
* with a graceful-shutdown handler (a .NET host, `ng serve` under a trapping
|
|
362
|
+
* shell) simply outlives — and since the caller then dropped the pid, the
|
|
363
|
+
* survivor became unreachable forever. Escalation plus a verified verdict is
|
|
364
|
+
* what makes "reaped" mean reaped.
|
|
365
|
+
*
|
|
366
|
+
* Returns true only when the observed and supplied recorded groups are empty.
|
|
367
|
+
* `observeGroup` hands the current group to reapPids before signalling so its
|
|
368
|
+
* ownership survives even if the recorded process exits during reclamation.
|
|
369
|
+
*/
|
|
370
|
+
export async function killGroupVerified(pid, recordedStart, graceMs = 2000, recordedGroup, observeGroup) {
|
|
371
|
+
if (!sameProcess(pid, recordedStart)) {
|
|
372
|
+
// The pid is no longer the process we recorded. Whatever now sits in that
|
|
373
|
+
// group may be a stranger's — our old children and an unrelated new leader
|
|
374
|
+
// are indistinguishable by pid alone — so signalling it is never safe.
|
|
375
|
+
// Report gone only if the group is genuinely empty; otherwise leave it
|
|
376
|
+
// unresolved for tag-based reclaim, which pid reuse cannot confuse.
|
|
377
|
+
return !groupAlive(recordedGroup ?? pid);
|
|
378
|
+
}
|
|
379
|
+
// Resolve the group BEFORE signalling: once the leader exits its /proc entry
|
|
380
|
+
// is gone and the surviving siblings become unattributable.
|
|
381
|
+
const pgid = processGroup(pid) ?? pid;
|
|
382
|
+
observeGroup?.(pgid);
|
|
383
|
+
const gone = () => !groupAlive(pgid) && (recordedGroup === undefined || !groupAlive(recordedGroup));
|
|
384
|
+
signalGroup(pgid, pid, 'SIGTERM');
|
|
385
|
+
const deadline = Date.now() + graceMs;
|
|
386
|
+
while (Date.now() < deadline) {
|
|
387
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
388
|
+
if (gone())
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
// The leader dying is NOT the success condition. `sh -c` commonly forks, so
|
|
392
|
+
// the wrapper exits on SIGTERM while the real server — which ignored it —
|
|
393
|
+
// keeps its port and its ~1 GB. Escalate against the whole group.
|
|
394
|
+
signalGroup(pgid, pid, 'SIGKILL');
|
|
395
|
+
// SIGKILL is not instantaneous: the kernel still has to tear the group down,
|
|
396
|
+
// and reporting "reaped" early would let the caller drop the pid one poll
|
|
397
|
+
// before the process is actually off the table.
|
|
398
|
+
const hard = Date.now() + 2000;
|
|
399
|
+
while (Date.now() < hard) {
|
|
400
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
401
|
+
if (gone())
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
return gone();
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* SIGTERM one process, then SIGKILL it after `graceMs` — never its group, and
|
|
408
|
+
* only while it is still the process recorded with `recordedStart`. For a
|
|
409
|
+
* process that shares a group with something runly does not own (`runly db
|
|
410
|
+
* with`'s command at a terminal, decision 0039). True once it is gone.
|
|
411
|
+
*/
|
|
412
|
+
export async function killPidVerified(pid, recordedStart, graceMs = 2000) {
|
|
413
|
+
const ours = () => sameProcess(pid, recordedStart);
|
|
414
|
+
if (!ours())
|
|
415
|
+
return true;
|
|
416
|
+
try {
|
|
417
|
+
process.kill(pid, 'SIGTERM');
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
return !ours();
|
|
421
|
+
}
|
|
422
|
+
const deadline = Date.now() + graceMs;
|
|
423
|
+
while (Date.now() < deadline) {
|
|
424
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
425
|
+
if (!ours())
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
process.kill(pid, 'SIGKILL');
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
/* gone meanwhile */
|
|
433
|
+
}
|
|
434
|
+
const hard = Date.now() + 2000;
|
|
435
|
+
while (Date.now() < hard) {
|
|
436
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
437
|
+
if (!ours())
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
return !ours();
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Kill PIDs recorded by a previous daemon life (recovery, decision 0009).
|
|
444
|
+
*
|
|
445
|
+
* Returns the entries that were NOT confirmed dead. The caller must keep those
|
|
446
|
+
* in the journal — a forgotten pid is an orphan nobody can ever reclaim.
|
|
447
|
+
*/
|
|
448
|
+
export async function reapPids(pids, kill = killGroupVerified) {
|
|
449
|
+
const survivors = {};
|
|
450
|
+
await Promise.all(Object.entries(pids).map(async ([name, rec]) => {
|
|
451
|
+
// Discovery can record nonleaders, and a process can move groups. Keep
|
|
452
|
+
// every observed live group even after its identifying process exits;
|
|
453
|
+
// only a verified process identity authorizes signalling a current group.
|
|
454
|
+
const groups = new Set(serviceGroups(rec));
|
|
455
|
+
if (sameProcess(rec.pid, rec.startTime))
|
|
456
|
+
groups.add(processGroup(rec.pid) ?? rec.pid);
|
|
457
|
+
const dead = await kill(rec.pid, rec.startTime, undefined, rec.pgid, group => groups.add(group));
|
|
458
|
+
const remaining = [...groups].filter(groupAlive);
|
|
459
|
+
if (remaining.length > 0)
|
|
460
|
+
survivors[name] = withServiceGroups(rec, remaining);
|
|
461
|
+
else if (!dead && sameProcess(rec.pid, rec.startTime))
|
|
462
|
+
survivors[name] = rec;
|
|
463
|
+
}));
|
|
464
|
+
return survivors;
|
|
465
|
+
}
|
|
466
|
+
export function serviceGroups(rec) {
|
|
467
|
+
return [...new Set([rec.pgid ?? rec.pid, ...(rec.pgids ?? [])])];
|
|
468
|
+
}
|
|
469
|
+
function withServiceGroups(rec, groups) {
|
|
470
|
+
const identity = { ...rec };
|
|
471
|
+
delete identity.pgid;
|
|
472
|
+
delete identity.pgids;
|
|
473
|
+
const [first, ...rest] = groups;
|
|
474
|
+
return { ...identity, ...(first !== rec.pid ? { pgid: first } : {}), ...(rest.length ? { pgids: rest } : {}) };
|
|
475
|
+
}
|
|
476
|
+
export function mergeServicePids(...sets) {
|
|
477
|
+
const merged = {};
|
|
478
|
+
for (const set of sets)
|
|
479
|
+
for (const [name, rec] of Object.entries(set ?? {})) {
|
|
480
|
+
const match = Object.entries(merged).find(([, other]) => other.pid === rec.pid && other.startTime === rec.startTime);
|
|
481
|
+
if (match) {
|
|
482
|
+
merged[match[0]] = withServiceGroups(rec, [...new Set([...serviceGroups(match[1]), ...serviceGroups(rec)])]);
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
let key = name;
|
|
486
|
+
while (Object.hasOwn(merged, key))
|
|
487
|
+
key += ':';
|
|
488
|
+
merged[key] = rec;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return merged;
|
|
492
|
+
}
|
|
493
|
+
//# sourceMappingURL=supervisor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../../src/daemon/supervisor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ApplianceSpec } from '../core/manifest.js';
|
|
2
|
+
export declare function probeTcp(target: string, timeoutMs?: number): Promise<boolean>;
|
|
3
|
+
export type ApplianceResult = 'up' | 'started' | 'adopted';
|
|
4
|
+
/**
|
|
5
|
+
* Probe; if down and a `start:` is declared, start it (once, machine-wide) and
|
|
6
|
+
* wait for the probe (and optional `ready:` gate) to accept. Throws infra-error
|
|
7
|
+
* when the appliance stays unreachable — with the start output when we tried.
|
|
8
|
+
*/
|
|
9
|
+
export declare function ensureAppliance(name: string, spec: ApplianceSpec, root: string, say: (msg: string) => void): Promise<ApplianceResult>;
|
|
10
|
+
/** Explicit stop verb only — runly never stops an appliance on its own. */
|
|
11
|
+
export declare function stopAppliance(name: string, spec: ApplianceSpec, root: string): Promise<void>;
|