| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677 |
- /** Bounded, escalating process shutdown for the long-lived CLI surfaces. */
- /** Maximum grace allowed for the application tree to dispose before process exit. */
- export const PROCESS_SHUTDOWN_TIMEOUT_MS = 5_000
- /** Process-exit controller shared by normal completion and Unix signal handlers. */
- export interface ProcessShutdown {
- /** Start or join graceful disposal before allowing natural completion with `code`. */
- shutdown(code: number): Promise<void>
- /** Start graceful disposal followed by exit, or force exit when shutdown is already running. */
- interrupt(code: number): void
- }
- /**
- * Create one process-exit controller around an application disposer.
- * @param dispose - Whole-application teardown that resolves at quiescence.
- * @param forceExit - Function that exits the process immediately, replaceable by tests.
- * @param complete - Function that records the natural completion code, replaceable by tests.
- * @param timeoutMs - Grace before forced exit, replaceable by tests.
- * @returns A controller whose normal calls coalesce and whose repeated signal call escalates.
- */
- export function createProcessShutdown(
- dispose: () => Promise<void>,
- forceExit: (code: number) => void = (code) => { process.exit(code) },
- complete: (code: number) => void = (code) => { process.exitCode = code },
- timeoutMs = PROCESS_SHUTDOWN_TIMEOUT_MS,
- ): ProcessShutdown {
- let pending: Promise<void> | undefined
- let timeout: ReturnType<typeof setTimeout> | undefined
- let completed = false
- let forceExited = false
- const clearExitTimeout = (): void => {
- /* v8 ignore else -- shutdown() arms the timer before any asynchronous exit path can run. */
- if (timeout !== undefined) clearTimeout(timeout)
- }
- const forceExitOnce = (code: number): void => {
- if (forceExited) return
- forceExited = true
- clearExitTimeout()
- forceExit(code)
- }
- const completeOnce = (code: number): void => {
- if (completed || forceExited) return
- completed = true
- clearExitTimeout()
- complete(code)
- }
- const start = (code: number, forceAfterDispose: boolean): Promise<void> => {
- if (pending !== undefined) return pending
- timeout = setTimeout(() => { forceExitOnce(code) }, timeoutMs)
- pending = Promise.resolve().then(dispose).then(
- () => {
- if (forceAfterDispose) forceExitOnce(code)
- else completeOnce(code)
- },
- () => { forceExitOnce(code) },
- )
- return pending
- }
- return {
- shutdown(code) {
- return start(code, false)
- },
- interrupt(code) {
- if (pending !== undefined) {
- forceExitOnce(code)
- return
- }
- void start(code, true)
- },
- }
- }
|