openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
262 lines (261 loc) • 7.61 kB
JavaScript
import { t as createDeferredCore } from "../../deferred-D0La5CRk.js";
import { n as GRACEFUL_CANCEL_TIMEOUT_MS, t as encodeServiceChildMessage } from "../../service-child-protocol-BVuC_Roy.js";
import { spawn } from "node:child_process";
import { Socket } from "node:net";
import { pipeline } from "node:stream";
//#region src/process/supervisor/service-child-group-anchor.ts
const LINEAGE_EXIT_OBSERVATION_MS = 100;
function commandStdio(start) {
const stdio = [
start.stdinMode === "inherit" ? "inherit" : "pipe",
"pipe",
"pipe"
];
if (start.secretFd !== void 0) {
while (stdio.length <= start.secretFd) stdio.push("ignore");
stdio[start.secretFd] = start.secretFd;
}
let lineageFd = 3;
while (stdio[lineageFd] !== void 0 && stdio[lineageFd] !== "ignore") lineageFd += 1;
while (stdio.length <= lineageFd) stdio.push("ignore");
stdio[lineageFd] = "pipe";
return {
stdio,
lineageFd
};
}
function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms).unref?.();
});
}
function runServiceChildGroupAnchor() {
let start;
let state = "starting";
let sequence = 0;
let lastHostSequence = 0;
let command;
let control;
let rootSettlementStarted = false;
let rootResultDelivery;
let rootExit;
let stdoutDrained = false;
let stderrDrained = false;
let lineageClosed = false;
let forceCleanup = false;
const forceCleanupRequested = createDeferredCore();
const lineageDone = createDeferredCore();
const rootExited = createDeferredCore();
const rootSettledDone = createDeferredCore();
const startupErrorAcknowledged = createDeferredCore();
const send = async (message) => {
if (!start || !control || control.destroyed) return;
sequence += 1;
await new Promise((resolve) => {
const framed = {
...message,
generation: start.generation,
sequence
};
control.write(encodeServiceChildMessage(framed), () => resolve());
});
};
const closeAuthority = async (reason, hardKill) => {
if (!start || state === "closed") return;
state = "closed";
await send({
type: "closing",
reason
});
if (hardKill) {
process.kill(0, "SIGKILL");
return;
}
control?.end(() => process.exit(0));
};
const reportStartupFailure = async (error) => {
await send({
type: "startup-error",
error
});
await startupErrorAcknowledged.promise;
await closeAuthority("lineage-lost", false);
};
const requestCleanup = async (reason, signal = "SIGTERM") => {
if (!start || state === "closed") return;
if (state === "closing") {
forceCleanup ||= signal === "SIGKILL";
if (forceCleanup) forceCleanupRequested.resolve();
return;
}
state = "closing";
forceCleanup = signal === "SIGKILL";
const termGraceDone = delay(GRACEFUL_CANCEL_TIMEOUT_MS);
if (!forceCleanup) {
process.kill(0, "SIGTERM");
await Promise.race([
lineageDone.promise,
termGraceDone,
forceCleanupRequested.promise
]);
}
if (state !== "closing" || !start) return;
if (lineageClosed && !rootExit && !forceCleanup) await Promise.race([
rootExited.promise,
termGraceDone,
forceCleanupRequested.promise
]);
if (state !== "closing" || !start) return;
if (lineageClosed && rootExit && !forceCleanup) {
await Promise.race([
rootSettledDone.promise,
termGraceDone,
forceCleanupRequested.promise
]);
if (state !== "closing" || !start) return;
}
await closeAuthority(reason, true);
};
const onControlMessage = (message) => {
if (!start || message.generation !== start.generation || message.sequence <= lastHostSequence || state === "closed") return;
lastHostSequence = message.sequence;
if (message.type === "startup-error-ack") {
startupErrorAcknowledged.resolve();
return;
}
requestCleanup("cancel", message.signal);
};
const startCommand = async (next) => {
if (next.controlFd === void 0) {
process.exitCode = 1;
return;
}
start = next;
control = new Socket({
fd: start.controlFd,
readable: true,
writable: true
});
control.setEncoding("utf8");
let pending = "";
control.on("data", (chunk) => {
pending += chunk;
for (;;) {
const newline = pending.indexOf("\n");
if (newline < 0) break;
const line = pending.slice(0, newline);
pending = pending.slice(newline + 1);
try {
onControlMessage(JSON.parse(line));
} catch {
requestCleanup("parent-lost");
}
}
});
control.once("close", () => {
if (state !== "closed") requestCleanup("parent-lost");
});
control.once("error", () => {
if (state !== "closed") requestCleanup("parent-lost");
});
const { stdio, lineageFd } = commandStdio(start);
try {
command = spawn(start.command, start.args, {
cwd: start.cwd,
env: start.env,
argv0: start.argv0,
stdio,
detached: false,
windowsHide: true
});
} catch (error) {
await reportStartupFailure(error instanceof Error ? error.message : String(error));
return;
}
const lineage = command.stdio[lineageFd];
if (!lineage) {
await send({
type: "startup-error",
error: "command lineage pipe was not created"
});
await requestCleanup("lineage-lost", "SIGKILL");
return;
}
const markLineageClosed = () => {
if (lineageClosed) return;
lineageClosed = true;
lineageDone.resolve();
if (state === "active") (async () => {
if (!rootExit) await Promise.race([rootExited.promise, delay(LINEAGE_EXIT_OBSERVATION_MS)]);
if (state !== "active") return;
if (rootExit && rootSettlementStarted) await rootSettledDone.promise;
if (state !== "active") return;
requestCleanup("lineage-lost");
})();
};
lineage.once("end", markLineageClosed);
lineage.once("close", markLineageClosed);
lineage.once("error", markLineageClosed);
const settleRoot = async () => {
if (rootSettlementStarted || !rootResultDelivery || !stdoutDrained || !stderrDrained) return;
rootSettlementStarted = true;
await rootResultDelivery;
rootSettledDone.resolve();
if (lineageClosed && state === "active") await closeAuthority("lineage-closed", false);
};
pipeline(command.stdout, process.stdout, () => {
stdoutDrained = true;
settleRoot();
});
pipeline(command.stderr, process.stderr, () => {
stderrDrained = true;
settleRoot();
});
if (start.stdinMode !== "inherit" && command.stdin) {
process.stdin.pipe(command.stdin);
if (start.stdinMode === "pipe-closed" && process.stdin.readableEnded) command.stdin.end();
}
command.once("error", (error) => {
if (state === "starting") reportStartupFailure(error.message);
});
command.once("spawn", () => {
if (!command?.pid || state !== "starting") return;
state = "active";
send({
type: "ready",
commandPid: command.pid,
anchorPid: process.pid
});
});
command.once("exit", (code, signal) => {
rootExit = {
code,
signal
};
rootResultDelivery = send({
type: "root-result",
code,
signal
});
rootExited.resolve();
settleRoot();
});
};
process.on("SIGTERM", () => {
if (state === "active") requestCleanup("parent-lost");
});
process.on("SIGINT", () => {
if (state === "active") requestCleanup("parent-lost");
});
process.once("disconnect", () => {
if (state !== "closed") requestCleanup("parent-lost");
});
process.on("message", (raw) => {
const message = raw;
if (message.type === "start" && state === "starting") startCommand(message);
else if (message.type === "parent-loss" && message.generation === start?.generation) requestCleanup("parent-lost");
});
}
runServiceChildGroupAnchor();
//#endregion
export { runServiceChildGroupAnchor };