Shutdown-Cleanup

Logo

NodeJS module to gracefully shutdown an application

View the Project on GitHub hypercliq/shutdown-cleanup

Developer Guide

This developer guide provides in-depth documentation on how to use the shutdown-cleanup module effectively in your Node.js applications, covering best practices, detailed examples, advanced usage scenarios, and guidance on migrating from earlier versions.

Introduction

The shutdown-cleanup module offers a structured approach to gracefully handle shutdown processes in Node.js applications. It supports:

Default Signal Handling and Customization

By default, the module listens to common signals (SIGINT, SIGTERM, SIGHUP, and beforeExit) to initiate the shutdown process. You can customize this behavior as needed.

Adding and Removing Signals

Customize your application’s response to system signals by adding or removing them:

import { addSignal, removeSignal } from '@hypercliq/shutdown-cleanup'

addSignal('SIGUSR1')
removeSignal('SIGHUP')

Signal-Specific Handlers

Gain flexibility by registering handlers for specific signals or events, allowing for custom logic without necessitating a shutdown:

import { registerHandler } from '@hypercliq/shutdown-cleanup'

registerHandler(
  async (signal) => {
    console.log('Custom logic for SIGUSR1')
  },
  {
    signal: 'SIGUSR1',
    shouldTerminate: false, // false indicates that the application should not terminate
  },
)

Handler Functions: Best Practices

Both synchronous and asynchronous functions are accepted as handlers. The module has taken careful steps to ensure that asynchronous handlers are handled properly, mitigating the potential issues warned about in the Node.js documentation.

Handling Asynchronous Handlers Safely

Node.js Warning on Async Event Handlers

The Node.js documentation cautions against using async functions directly as event handlers because unhandled promise rejections can occur if errors are not properly caught. This can lead to warnings or even cause the process to crash in certain Node.js versions.

Our Module’s Mitigation

The shutdown-cleanup module internally wraps the execution of your handlers to catch both synchronous exceptions and promise rejections. This ensures that even if your asynchronous handler throws an error or returns a rejected promise, the module will handle it appropriately, preventing unhandled promise rejections.

What This Means for You

Examples

Asynchronous Handler:

import { registerHandler } from '@hypercliq/shutdown-cleanup'

registerHandler(async (signal) => {
  await performAsyncCleanup()
  console.log('Asynchronous cleanup completed')
})

Synchronous Handler:

import { registerHandler } from '@hypercliq/shutdown-cleanup'

registerHandler((signal) => {
  performSyncCleanup()
  console.log('Synchronous cleanup completed')
})

Best Practices for Handlers

Advanced Configuration

Error Handling Strategies

Set how the module handles errors occurring in your handlers using setErrorHandlingStrategy:

import { setErrorHandlingStrategy } from '@hypercliq/shutdown-cleanup'

setErrorHandlingStrategy('stop')

Shutdown Timeout

Set a timeout for the shutdown process with setShutdownTimeout, ensuring the application doesn’t hang indefinitely:

import { setShutdownTimeout } from '@hypercliq/shutdown-cleanup'

setShutdownTimeout(20000) // 20 seconds - default is 30 seconds

Custom Exit Codes

Specify a custom exit code to be used when the application exits after the shutdown process:

import { setCustomExitCode } from '@hypercliq/shutdown-cleanup'

setCustomExitCode(42)

Examples

Basic Shutdown Sequence

A simple setup for managing a graceful shutdown:

import { registerHandler } from '@hypercliq/shutdown-cleanup'

registerHandler(async (signal) => {
  await performAsyncCleanup()
  console.log('Cleanup resources')
})

Advanced Shutdown Sequence

A comprehensive example demonstrating how to set up a phased graceful shutdown sequence with custom signal handling:

import {
  addSignal,
  registerHandler,
  setErrorHandlingStrategy,
  setCustomExitCode,
} from '@hypercliq/shutdown-cleanup'

// Register a phased shutdown handler to be executed in the default phase (phase 1)
registerHandler(
  async (signal) => {
    await releaseResources()
    console.log('Phase 1: Release resources')
  },
  {
    identifier: 'releaseResources', // Specify a unique identifier for the handler
  },
)

// Register a handler to be executed in phase 2
registerHandler(
  async (signal) => {
    await closeConnections()
    console.log('Phase 2: Close connections')
  },
  {
    identifier: 'closeConnections',
    phase: 2, // Specify the phase number
  },
)

// Add an extra lifecycle event to listen to; phased shutdown handlers will be executed for this event
addSignal('beforeExit')

// Register a signal-specific handler for SIGUSR1 that does not terminate the application
registerHandler(
  async (signal) => {
    console.log(`Handling ${signal} for debugging purposes`)
  },
  {
    signal: 'SIGUSR1',
    shouldTerminate: false, // The application will not terminate after this handler
  },
)

// Register a signal-specific handler for a custom signal raised somewhere in the application
registerHandler(
  async (signal) => {
    console.log(`Handling custom signal: ${signal}`)
  },
  {
    signal: 'customSignal', // Custom signal or event name
    shouldTerminate: true, // The application will terminate after this handler
  },
)

// Set the error handling strategy to 'continue'
setErrorHandlingStrategy('continue')

// Set a custom exit code
setCustomExitCode(42)

Migration Guide

If you are upgrading from a previous version of the shutdown-cleanup module, this section will help you migrate your code to the latest version.

Key Changes in the Latest Version

  1. Unified registerHandler Function: The functions registerSignalHandler and registerPhaseHandler have been unified into a single registerHandler function with options.

  2. Support for Both Synchronous and Asynchronous Handlers: The module now accepts both synchronous and asynchronous handler functions.

  3. Improved Error Handling: The module has enhanced error handling internally to catch both synchronous exceptions and promise rejections in handlers.

  4. TypeScript Definitions Updated: Type definitions have been updated to reflect the new API.

Migration Steps

1. Update Handler Registration

Before (Old Version):

import {
  registerHandler,
  registerSignalHandler,
} from '@hypercliq/shutdown-cleanup'

// Registering a phase handler
registerHandler(
  async (signal) => {
    await performCleanup()
  },
  'cleanupHandler',
  1,
)

// Registering a signal-specific handler
registerSignalHandler(
  'SIGUSR1',
  async (signal) => {
    console.log('Handling SIGUSR1')
  },
  false,
)

After (New Version):

import { registerHandler } from '@hypercliq/shutdown-cleanup'

// Registering a phase handler
registerHandler(
  async (signal) => {
    await performCleanup()
  },
  {
    identifier: 'cleanupHandler',
    phase: 1,
  },
)

// Registering a signal-specific handler
registerHandler(
  async (signal) => {
    console.log('Handling SIGUSR1')
  },
  {
    signal: 'SIGUSR1',
    shouldTerminate: false,
  },
)

2. Remove Deprecated Functions

3. Adjust Error Handling Strategy

4. Verify Handlers

5. Update TypeScript Imports (If Applicable)

6. Test Thoroughly

Example Migration

Before Migration:

import {
  registerHandler,
  registerSignalHandler,
} from '@hypercliq/shutdown-cleanup'

// Phase handler
registerHandler(
  async (signal) => {
    await cleanUpResources()
  },
  'resourceCleanup',
  1,
)

// Signal-specific handler
registerSignalHandler(
  'SIGUSR2',
  async (signal) => {
    console.log('Received SIGUSR2')
  },
  true,
)

After Migration:

import { registerHandler } from '@hypercliq/shutdown-cleanup'

// Phase handler
registerHandler(
  async (signal) => {
    await cleanUpResources()
  },
  {
    identifier: 'resourceCleanup',
    phase: 1,
  },
)

// Signal-specific handler
registerHandler(
  async (signal) => {
    console.log('Received SIGUSR2')
  },
  {
    signal: 'SIGUSR2',
    shouldTerminate: true,
  },
)

API Reference

This section provides a detailed overview of the functions available in the shutdown-cleanup module, allowing for effective management of your application’s shutdown process.

(The API Reference remains the same as detailed in the earlier sections.)

Type Definitions

For TypeScript users, the module includes type definitions to enhance development experience.

(Type Definitions remain the same as detailed in the earlier sections.)

Additional Tips and Best Practices

Conclusion

The shutdown-cleanup module provides a robust and flexible solution for managing graceful shutdowns in Node.js applications. By following the best practices outlined in this guide and leveraging the advanced features of the module, you can ensure that your application gracefully handles shutdowns, even in complex scenarios.