Download src/process/child-process.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 2.21 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/process/child-process.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/process/child-process.ts
-
curl -L -o child-process.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/process/child-process.ts
2.21 kB
| /** | |
| * Child-process output cleanup for commands whose descendants inherit pipes. | |
| */ | |
| import type { ChildProcess } from "node:child_process"; | |
| const EXIT_STDIO_GRACE_MS = 100; | |
| const EXIT_STDIO_MAX_DRAIN_MS = 1_000; | |
| /** | |
| * Execa waits for stdout/stderr after the direct child exits. Bound that wait | |
| * when detached descendants keep inherited pipes open, while still draining | |
| * short output tails. The returned cleanup must run after awaiting the child. | |
| */ | |
| export function releaseChildProcessOutputAfterExit(child: ChildProcess): () => void { | |
| let idleTimer: NodeJS.Timeout | undefined; | |
| let releaseTimer: NodeJS.Timeout | undefined; | |
| let deadlineTimer: NodeJS.Timeout | undefined; | |
| const cleanup = () => { | |
| clearTimeout(idleTimer); | |
| clearTimeout(releaseTimer); | |
| clearTimeout(deadlineTimer); | |
| child.removeListener("exit", onExit); | |
| child.stdout?.removeListener("data", onData); | |
| child.stderr?.removeListener("data", onData); | |
| }; | |
| const release = () => { | |
| cleanup(); | |
| child.stdout?.destroy(); | |
| child.stderr?.destroy(); | |
| }; | |
| const scheduleRelease = () => { | |
| // Either timer may run before already-buffered pipe data on a loaded loop. | |
| // Defer to the next timers phase so both Node and Bun poll the pipes first. | |
| releaseTimer ??= setTimeout(release, 0); | |
| releaseTimer.unref(); | |
| }; | |
| const armIdleTimer = () => { | |
| clearTimeout(idleTimer); | |
| clearTimeout(releaseTimer); | |
| releaseTimer = undefined; | |
| idleTimer = setTimeout(scheduleRelease, EXIT_STDIO_GRACE_MS); | |
| idleTimer.unref(); | |
| }; | |
| const onData = () => { | |
| if (deadlineTimer) { | |
| armIdleTimer(); | |
| } | |
| }; | |
| const onExit = () => { | |
| deadlineTimer = setTimeout(() => { | |
| // Post-deadline data must not cancel release and extend the hard bound. | |
| deadlineTimer = undefined; | |
| scheduleRelease(); | |
| }, EXIT_STDIO_MAX_DRAIN_MS); | |
| deadlineTimer.unref(); | |
| armIdleTimer(); | |
| }; | |
| child.stdout?.on("data", onData); | |
| child.stderr?.on("data", onData); | |
| // A command deadline can transfer output here after the root has already exited. | |
| if (child.exitCode != null || child.signalCode != null) { | |
| onExit(); | |
| } else { | |
| child.once("exit", onExit); | |
| } | |
| return cleanup; | |
| } | |