close-with-grace

repository·main·Indexed 43 days ago

https://github.com/mcollina/close-with-grace

A Node.js utility for graceful process shutdowns. It listens for common termination signals (like SIGINT, SIGTERM) and unhandled errors (uncaughtException, unhandledRejection), allowing developers to execute cleanup logic before the process exits. Version 2.5.0 includes features such as timeout protection via a configurable delay, custom logger injection, and the ability to skip specific process events.

Tokens
2.5K
Snippets
7
Records
10
Agent score
5%

What's inside close-with-grace

  1. Understand the shutdown callback arguments

    main

    The callback function fn provided to closeWithGrace receives a single object containing the reason for the shutdown:

    • err: If the shutdown was triggered by a process error, this contains the Error object.
    • signal: If the shutdown was triggered by a process signal (e.g., 'SIGTERM'), this contains the signal name string.
    • manual: If the shutdown was triggered by calling the returned close() method, this is present (though the implementation uses manual: true in the run call, the object structure is passed to your function).
  2. Inject a custom logger

    main

    By default, close-with-grace uses console. You can provide a custom logger object with an error method to control how internal logs are handled. Pass false to disable logging entirely.

    const closeWithGrace = require('close-with-grace')
    
    closeWithGrace(
      {
        delay: 500,
        logger: { error: (m) => console.error(`[close-with-grace] ${m}`) }
      },
      async function ({ signal, err, manual }) {
      if (err) {
        console.error(err)
      }
      await closeYourServer()
    })
    
    // to disable logging at all, pass falsy value to logger option.
    closeWithGrace({ logger: false }, () => await somethingUseful())
  3. Example: Integrating with Fastify

    main

    When using Fastify, use the closeWithGrace callback to call app.close(). You can use the signal or err properties to log the reason for shutdown using Fastify's built-in logger.

    import fastify from 'fastify'
    import closeWithGrace from 'close-with-grace'
    
    const app = fastify()
    
    closeWithGrace(async function ({ signal, err, manual }) {
      if (err) {
        app.log.error({ err }, 'server closing with error')
      } else {
        app.log.info(`${signal} received, server closing`)
      }
      await app.close()
    })
    
    await app.listen()
  4. Basic usage of closeWithGrace

    main

    Use closeWithGrace to wrap your cleanup logic. You provide an options object (optional) and an async function (or a function using a callback) that performs the cleanup. The function receives an object containing signal, err, and manual.

    const closeWithGrace = require('close-with-grace')
    
    // delay is the number of milliseconds for the graceful close to
    // finish.
    closeWithGrace({ delay: 500 }, async function ({ signal, err, manual }) {
      if (err) {
        console.error(err)
      }
      await closeYourServer()
    })
    
    // default delay is 10000
    // to disable delay feature at all, pass falsy value to delay option.
    closeWithGrace({ delay: false }, () => await somethingUseful())
  5. Skip specific process events

    main

    You can prevent certain events from triggering the graceful shutdown by providing an array of event names to the skip option.

    Warning: If you skip an event, you are responsible for handling it yourself (e.g., via process.on(...)), otherwise the process may crash or exit unexpectedly.

    import closeWithGrace from 'close-with-grace'
    
    // Handle errors separately
    process.on('unhandledRejection', (err) => {
      // Your custom error handling
    })
    
    closeWithGrace(
      { skip: ['unhandledRejection', 'uncaughtException'] },
      async function ({ signal, err, manual }) {
        await cleanupResources()
      }
    )
  6. API Reference: closeWithGrace()

    main

    The closeWithGrace function adds global listeners to several Node.js process events (including SIGINT, SIGTERM, uncaughtException, and unhandledRejection). When an event is emitted, the provided function is called. If the same event is emitted again while the function is still running, the process terminates abruptly.

    Arguments

    opts (Object)

    • delay (number): Milliseconds to wait before abruptly closing the process. Default: 10000. Pass false, null, or undefined to disable.
    • logger (Object): Logger instance with an error method. Default: console. Pass false, null, or undefined to disable.
    • skip (Array<string>): Event names to skip from triggering the callback. Default: [].
    • onSecondError(error) (Function): Callback executed if an uncaughtException or unhandledRejection occurs while the cleanup function is executing.
    • onSecondSignal(signal) (Function): Callback executed if another signal is received while the cleanup function is executing.
    • onTimeout(delay) (Function): Callback executed if the cleanup function fails to complete within the specified delay.

    Note: onSecondError, onSecondSignal, and onTimeout callbacks cannot perform asynchronous operations because process.exit(1) is invoked immediately after they return.

    fn({ err, signal, manual } [, cb]) (Function)

    • The cleanup function. Can return a Promise or use a callback.
    • If the function succeeds, the process exits with code 0.
    • If the function rejects or calls the callback with an Error, the process exits with code 1.
    • err: The error that triggered the shutdown (if any).
    • signal: The signal that triggered the shutdown (if any).
    • manual: Boolean indicating if the shutdown was triggered manually via the returned close() method.

    Return Value

    Returns an object containing:

    • close(): Triggers a graceful close; sets manual to true in the callback.
    • uninstall(): Removes all global listeners added by closeWithGrace.
    // Signature
    closeWithGrace([opts], fn({ err, signal, manual }))
  7. Configure closeWithGrace options

    main

    When calling closeWithGrace(opts, fn), the opts object supports the following configuration keys:

    KeyTypeDefaultDescription
    delaynumber10000Maximum time (in milliseconds) to wait for the cleanup function to finish before forcing exit.
    loggerobject or functionconsoleA logger object (must have an .error() method) or a function used for logging errors and timeouts.
    skipArray<string>[]An array of process event names (e.g., 'SIGINT') to ignore.
    onSecondSignalfunctionundefinedCallback invoked if a second signal is received during shutdown.
    onSecondErrorfunctionundefinedCallback invoked if a second error occurs during shutdown.
    onTimeoutfunctionundefinedCallback invoked if the cleanup process is killed by the delay timeout.
  8. Use closeWithGrace to handle graceful shutdowns

    main

    The closeWithGrace function manages process signals (like SIGTERM), errors, and normal exits to ensure your application can perform cleanup tasks before shutting down.

    It accepts an optional configuration object and a callback function. The callback function is executed when a shutdown event occurs. The callback can be async (returning a Promise) or use a done callback to signal completion.

    Key Features:

    • Automatic Signal Handling: Listens for process signals, errors, and exit events.
    • Timeout Protection: If the cleanup function takes longer than the specified delay, the process is forcibly killed.
    • Second Signal Protection: If a second signal or error occurs while the first is being handled, the process exits immediately to prevent hanging.
    • Manual Control: Returns an object with close() to trigger shutdown manually and uninstall() to remove all event listeners.
    const closeWithGrace = require('close-with-grace');
    
    const { close, uninstall } = closeWithGrace({
      delay: 5000,
      logger: console
    }, async ({ err, signal, manual }) => {
      console.log('Cleaning up...');
      // Perform async cleanup here
      await someCleanupTask();
      console.log('Cleanup finished');
    });
    
    // To trigger shutdown manually:
    // await close();
    
    // To stop listening to process events:
    // uninstall();
  9. Handle cleanup with async/await or a callback

    main

    The cleanup function passed to closeWithGrace can be implemented in two ways:

    1. Async/Await (Recommended): Return a Promise that resolves when cleanup is complete.

    closeWithGrace({}, async () => {
      await performCleanup();
    });

    2. Callback style: Use the done argument provided as the second parameter to the function. Call done() with no arguments on success, or done(err) if an error occurred.

    closeWithGrace({}, (out, done) => {
      performCleanup((err) => {
        if (err) return done(err);
        done();
      });
    });