(
eventName: string,
evtOrExitCodeOrError: number | string | Error
)
| 325 | }); |
| 326 | |
| 327 | async function exitHandler( |
| 328 | eventName: string, |
| 329 | evtOrExitCodeOrError: number | string | Error |
| 330 | ) { |
| 331 | logger.info( |
| 332 | { code: evtOrExitCodeOrError, eventName }, |
| 333 | 'Starting graceful shutdown' |
| 334 | ); |
| 335 | |
| 336 | // Hard deadline: if cron drain or worker.close() hangs, force-exit |
| 337 | // before Docker's stop_grace_period elapses. Without this the |
| 338 | // container sits in "Stopping" until SIGKILL (exit 137) and looks |
| 339 | // like a real crash to the swarm. |
| 340 | const forceExitMs = Number( |
| 341 | process.env.SHUTDOWN_FORCE_EXIT_MS || '20000' |
| 342 | ); |
| 343 | const exitCode = Number.isNaN(+evtOrExitCodeOrError) |
| 344 | ? 1 |
| 345 | : +evtOrExitCodeOrError; |
| 346 | const forceExit = setTimeout(() => { |
| 347 | logger.error( |
| 348 | { eventName, forceExitMs }, |
| 349 | 'Graceful shutdown timed out — forcing exit' |
| 350 | ); |
| 351 | process.exit(exitCode); |
| 352 | }, forceExitMs); |
| 353 | forceExit.unref(); |
| 354 | |
| 355 | try { |
| 356 | const time = performance.now(); |
| 357 | |
| 358 | // Wait for cron queue to empty if it's running |
| 359 | if (enabledQueues.includes('cron')) { |
| 360 | await waitForQueueToEmpty(cronQueue); |
| 361 | } |
| 362 | |
| 363 | await Promise.all([ |
| 364 | ...workers.map((worker) => worker.close()), |
| 365 | ...extraStops.map((stop) => |
| 366 | stop().catch((err) => { |
| 367 | logger.error({ err }, 'extra stop handler error'); |
| 368 | }) |
| 369 | ), |
| 370 | ]); |
| 371 | |
| 372 | logger.info( |
| 373 | { elapsed: performance.now() - time }, |
| 374 | 'workers closed successfully' |
| 375 | ); |
| 376 | } catch (e) { |
| 377 | logger.error( |
| 378 | { err: e, code: evtOrExitCodeOrError }, |
| 379 | 'exit handler error' |
| 380 | ); |
| 381 | } |
| 382 | clearTimeout(forceExit); |
| 383 | process.exit(exitCode); |
| 384 | } |
no test coverage detected