mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-09 22:25:02 +03:00
fix(util): kill spawned process groups when opencode exits (#59411)
This commit is contained in:
committed by
GitHub
Unverified
parent
f97f82c96f
commit
e2bc79dc0b
@@ -36,14 +36,20 @@ export const install = Effect.fnUntraced(function* (input: Duration.Input = DEAD
|
||||
started = true
|
||||
fork(expire(signal))
|
||||
}
|
||||
// Closing the terminal sends SIGHUP, whose default action exits without teardown. Treat it as SIGTERM, but only
|
||||
// with a terminal: nohup and the background service have none, and a listener would override nohup's ignore.
|
||||
const onHangup = () => process.kill(process.pid, "SIGTERM")
|
||||
const hangup = process.stdin.isTTY || process.stdout.isTTY
|
||||
const off = () => {
|
||||
process.off("SIGTERM", onSignal)
|
||||
process.off("SIGINT", onSignal)
|
||||
process.off("SIGHUP", onHangup)
|
||||
}
|
||||
yield* Effect.acquireRelease(
|
||||
Effect.sync(() => {
|
||||
process.on("SIGTERM", onSignal)
|
||||
process.on("SIGINT", onSignal)
|
||||
if (hangup) process.on("SIGHUP", onHangup)
|
||||
}),
|
||||
// Once shutdown has started, keep listening: this release can run while a later finalizer is still stuck.
|
||||
() =>
|
||||
|
||||
@@ -23,6 +23,19 @@ import { Teardown } from "./teardown.js"
|
||||
/** Closing a child's scope escalates to SIGKILL after this, unless the command sets its own `forceKillAfter`. */
|
||||
export const RELEASE_FORCE_KILL_AFTER = "3 seconds"
|
||||
|
||||
// Process groups of running children. Exiting without stopping them (a shutdown deadline, a second signal, a
|
||||
// crash) would orphan them, so a synchronous exit hook kills whatever is left.
|
||||
const groups = new Set<number>()
|
||||
let hooked = false
|
||||
const killGroups = () =>
|
||||
groups.forEach((pid) => {
|
||||
try {
|
||||
globalThis.process.kill(-pid, "SIGKILL")
|
||||
} catch {
|
||||
// Already gone.
|
||||
}
|
||||
})
|
||||
|
||||
const toError = (err: unknown): Error => (err instanceof globalThis.Error ? err : new globalThis.Error(String(err)))
|
||||
|
||||
const toTag = (err: NodeJS.ErrnoException): PlatformError.SystemErrorTag => {
|
||||
@@ -297,6 +310,7 @@ const makeCrossSpawnSpawner = Effect.gen(function* () {
|
||||
resume(Effect.fail(toPlatformError("spawn", err, command)))
|
||||
})
|
||||
proc.on("exit", (...args) => {
|
||||
groups.delete(proc.pid!)
|
||||
exit = args
|
||||
Deferred.doneUnsafe(exited, Exit.succeed(args))
|
||||
})
|
||||
@@ -308,6 +322,11 @@ const makeCrossSpawnSpawner = Effect.gen(function* () {
|
||||
Deferred.doneUnsafe(closed, Exit.succeed(exit ?? args))
|
||||
})
|
||||
proc.on("spawn", () => {
|
||||
if (opts.detached && globalThis.process.platform !== "win32") {
|
||||
if (!hooked) globalThis.process.once("exit", killGroups)
|
||||
hooked = true
|
||||
groups.add(proc.pid!)
|
||||
}
|
||||
settle = Teardown.track(
|
||||
`child ${proc.pid} running: ${[command.command, ...command.args].join(" ").slice(0, 200)}`,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user