All files / src/utils process-shutdown.js

100% Statements 10/10
100% Branches 2/2
100% Functions 3/3
100% Lines 10/10

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45                                          17x 12x 12x   20x 20x   4x           12x     17x 17x     1x      
/**
 * Wire process-level SIGTERM/SIGINT to a shared shutdown handler that
 * invokes every supplied `stop` function (e.g. event-listener detach
 * functions) before calling `process.exit(0)`.
 *
 * Without this wiring, PM2's graceful-restart SIGTERM kills the Node
 * process mid-RTDB-listener-processing, leaving signal entries in RTDB
 * in an ambiguous "did the Firestore txn commit?" state. With it, the
 * listener detaches cleanly so in-flight signals either complete or are
 * left for the next process boot to pick up via the startup-scan.
 *
 * Async stops ARE supported — the handler awaits every stop (in parallel
 * via Promise.allSettled) before calling proc.exit. Sync stops work
 * unchanged. Errors from any individual stop are caught + logged but do
 * not prevent other stops or the final exit.
 *
 * Dependencies are injected to keep this unit-testable without mocking
 * the global `process` object.
 */
 
function wireProcessShutdown({ proc, stopFns, log }) {
  const handler = async (signal) => {
    log.info('process-shutdown', `Received ${signal}, stopping listeners`, { signal });
    await Promise.allSettled(
      stopFns.map(async (stop) => {
        try {
          await stop();
        } catch (err) {
          log.warn('process-shutdown', 'stop function threw during shutdown', {
            error: err && err.message,
          });
        }
      }),
    );
    proc.exit(0);
  };
 
  proc.on('SIGTERM', handler);
  proc.on('SIGINT', handler);
}
 
module.exports = {
  wireProcessShutdown,
};