Download src/daemon/hosted-stop-executor.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 5.44 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/daemon/hosted-stop-executor.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/daemon/hosted-stop-executor.ts
-
curl -L -o hosted-stop-executor.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/daemon/hosted-stop-executor.ts
5.44 kB
| import { spawn } from "node:child_process"; | |
| import { quoteCliArg } from "../cli/quote-cli-arg.js"; | |
| import { formatErrorMessage } from "../infra/errors.js"; | |
| type NativeStopDisposition = "accepted" | "refused" | "uncertain"; | |
| // The shell waits for a separate authorization line. EOF cancels before exec; | |
| // preparing its script cannot issue a native stop. exec preserves the owned PID. | |
| const STOP_EXECUTOR_SCRIPT = | |
| 'printf "%s\\n" "$$"; IFS= read -r action && [ "$action" = stop ] && exec "$@"'; | |
| /** A non-exiting executor, optionally placed outside the service's kill cgroup. */ | |
| export async function prepareHostedStopExecutor(params: { | |
| command: string[]; | |
| scopeArgs?: string[]; | |
| env: NodeJS.ProcessEnv; | |
| signal: AbortSignal; | |
| assertCurrent: () => void; | |
| verifyPlacement?: (pid: number) => Promise<void>; | |
| }) { | |
| params.assertCurrent(); | |
| params.signal.throwIfAborted(); | |
| // systemd versions differ on argv dollar expansion. Send the quoted command | |
| // and handshake over stdin so neither old nor new managers can rewrite them. | |
| const script = `set -- ${params.command.map(quoteCliArg).join(" ")}; ${STOP_EXECUTOR_SCRIPT}\n`; | |
| const command = params.scopeArgs ? "systemd-run" : "/bin/sh"; | |
| const args = params.scopeArgs ? [...params.scopeArgs, "/bin/sh"] : []; | |
| // A native group stop must reach the Gateway before it kills this client. | |
| // Detached creates a separate process group; the private pipe and close join | |
| // still keep the executor owned, with no unref or unattended continuation. | |
| const child = spawn(command, args, { | |
| env: params.env, | |
| detached: true, | |
| stdio: ["pipe", "pipe", "pipe"], | |
| }); | |
| let output = ""; | |
| let diagnostic = ""; | |
| let committed = false; | |
| let disposed = false; | |
| let ready = false; | |
| let resolveReady: (pid: number) => void; | |
| let rejectReady: (error: Error) => void; | |
| const readiness = new Promise<number>((resolve, reject) => { | |
| resolveReady = resolve; | |
| rejectReady = reject; | |
| }); | |
| let resolveResult: (result: { disposition: NativeStopDisposition; detail: string }) => void; | |
| const result = new Promise<{ disposition: NativeStopDisposition; detail: string }>((resolve) => { | |
| resolveResult = resolve; | |
| }); | |
| const closed = result.then(() => {}); | |
| let timeout: ReturnType<typeof setTimeout>; | |
| const dispose = () => { | |
| if (disposed) { | |
| return closed; | |
| } | |
| disposed = true; | |
| clearTimeout(timeout); | |
| params.signal.removeEventListener("abort", cancel); | |
| child.stdin.end(); | |
| // Kill only this owned executor, then join close (including its stdio). | |
| // A cancelled command is uncertain even if it happened to exit successfully. | |
| if (child.exitCode === null && child.signalCode === null) { | |
| child.kill("SIGKILL"); | |
| } | |
| return closed; | |
| }; | |
| const cancel = () => { | |
| void dispose(); | |
| }; | |
| params.signal.addEventListener("abort", cancel, { once: true }); | |
| child.stdin.on("error", (error) => { | |
| diagnostic = formatErrorMessage(error); | |
| cancel(); | |
| }); | |
| child.stderr.on("data", (data: Buffer) => { | |
| diagnostic = (diagnostic + data.toString()).slice(-1000); | |
| }); | |
| child.stdout.on("data", (data: Buffer) => { | |
| if (ready) { | |
| return; | |
| } | |
| output += data.toString(); | |
| if (output.length > 128) { | |
| rejectReady(new Error("Native stop executor returned invalid readiness")); | |
| cancel(); | |
| return; | |
| } | |
| if (output.endsWith("\n")) { | |
| if (!/^[1-9]\d*\n$/.test(output) || Number(output.trim()) !== child.pid) { | |
| rejectReady(new Error("Native stop executor returned invalid readiness")); | |
| cancel(); | |
| return; | |
| } | |
| ready = true; | |
| clearTimeout(timeout); | |
| resolveReady(Number(output.trim())); | |
| } | |
| }); | |
| child.once("error", (error) => { | |
| diagnostic = formatErrorMessage(error); | |
| rejectReady(new Error(`Native stop executor unavailable: ${diagnostic}`)); | |
| }); | |
| child.once("close", (code, signal) => { | |
| clearTimeout(timeout); | |
| params.signal.removeEventListener("abort", cancel); | |
| rejectReady(new Error(`Native stop executor unavailable: ${diagnostic || "closed"}`)); | |
| resolveResult({ | |
| disposition: | |
| !disposed && committed && code === 0 && !signal | |
| ? "accepted" | |
| : !disposed && committed && typeof code === "number" && !signal | |
| ? "refused" | |
| : "uncertain", | |
| detail: diagnostic, | |
| }); | |
| }); | |
| timeout = setTimeout(() => { | |
| rejectReady(new Error("Native stop executor preparation timed out")); | |
| cancel(); | |
| }, 5_000); | |
| try { | |
| child.stdin.write(script); | |
| const pid = await readiness; | |
| params.signal.throwIfAborted(); | |
| params.assertCurrent(); | |
| await params.verifyPlacement?.(pid); | |
| params.signal.throwIfAborted(); | |
| params.assertCurrent(); | |
| return { | |
| dispose, | |
| execute(assertCurrent: () => void) { | |
| assertCurrent(); | |
| params.signal.throwIfAborted(); | |
| if (committed) { | |
| throw new Error("Native stop executor is no longer available"); | |
| } | |
| if (disposed || child.exitCode !== null || child.signalCode !== null) { | |
| return Promise.resolve({ | |
| disposition: "refused" as const, | |
| detail: "Native executor closed before stop was requested", | |
| }); | |
| } | |
| committed = true; | |
| child.stdin.write("stop\n"); | |
| timeout = setTimeout(cancel, 5_000); | |
| return result; | |
| }, | |
| }; | |
| } catch (error) { | |
| await dispose(); | |
| throw error; | |
| } | |
| } | |