diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 6dc5e99e91ef..d9806814deb6 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -26,6 +26,7 @@ import { EOL } from "os" import { WebCommand } from "./cli/cmd/web" import { PrCommand } from "./cli/cmd/pr" import { SessionCommand } from "./cli/cmd/session" +import { registerSignalHandlers } from "./signal" process.on("unhandledRejection", (e) => { Log.Default.error("rejection", { @@ -39,6 +40,8 @@ process.on("uncaughtException", (e) => { }) }) +registerSignalHandlers() + const cli = yargs(hideBin(process.argv)) .parserConfiguration({ "populate--": true }) .scriptName("opencode") diff --git a/packages/opencode/src/signal.ts b/packages/opencode/src/signal.ts new file mode 100644 index 000000000000..9c84cb1788b8 --- /dev/null +++ b/packages/opencode/src/signal.ts @@ -0,0 +1,47 @@ +import { Instance } from "./project/instance" +import { Log } from "./util/log" + +const SHUTDOWN_TIMEOUT_MS = 5000 + +const SIGNAL_EXIT_CODES: Record = { + SIGTERM: 128 + 15, + SIGINT: 128 + 2, + SIGHUP: 128 + 1, + SIGQUIT: 128 + 3, +} + +let shuttingDown = false + +async function gracefulShutdown(signal: string) { + if (shuttingDown) return + shuttingDown = true + + const log = Log.create({ service: "signal" }) + log.info("received signal, shutting down gracefully", { signal }) + + const timeout = setTimeout(() => { + log.warn("shutdown timeout, forcing exit", { + timeoutMs: SHUTDOWN_TIMEOUT_MS, + signal, + }) + process.exit(SIGNAL_EXIT_CODES[signal] ?? 1) + }, SHUTDOWN_TIMEOUT_MS) + timeout.unref() + + try { + await Instance.disposeAll() + } catch (error) { + log.error("error during shutdown", { error }) + } finally { + clearTimeout(timeout) + process.exit(SIGNAL_EXIT_CODES[signal] ?? 0) + } +} + +export function registerSignalHandlers() { + for (const signal of ["SIGTERM", "SIGINT", "SIGHUP", "SIGQUIT"]) { + process.on(signal, () => { + void gracefulShutdown(signal) + }) + } +}