Node Graceful-Critical
Mar 2, 2019
4 minute read

My first public npm package!

TLDR

A simple module to prevent a Nodejs program from exiting during critical sections of code.

The Problem

Whether you are building an API or some other service, how your application startups and shutdowns can be just as important as what it is supposed to do. Working with Docker containers and Kubernetes, have you ever asked yourself what happens to your application when the container is terminated? Do you have sections of code that if you exited during would negatively impact your application or data. Most will be aware that Express and other frameworks offer simple solutions to ensure a graceful and timely exit, graceful-critical is a simple approach to protecting critical sections by preventing the process from exiting. Graceful-critical focuses on non-API applications that need to protect critical sections.

Two personal examples of applications I have created in which I used graceful-critical were an SSH file gateway and event process worker. Both of these applications have sections of code that once entered should be completed before exiting. I need the same functionality in both packages so i decided to write my first npm package.

Express Graceful Exit Example

Express and other API based application can be graceful around http connections. Below is a simple example of a graceful exit of an Express app. Treating ever client request a critical section, Express offers a .close() method, which both waits for and blocks remaining client connections.


const express = require('express');
const app = express();

// Express App

const server = app.listen(3000, () => {
  console.log('Server listening on port 3000!');
});

process.once('SIGTERM', () => {
  console.log('Received kill signal, attempting gracefully close');
  server.close(() => {
    console.log('Closed out remaining connections');
    process.kill(process.pid, 'SIGTERM');
  });
  setTimeout(() => {
    console.error('Timeout, forcefully shutting down');
    process.kill(process.pid, 'SIGTERM');
  }, 3000);
});

Terminus and Lightship

Since creating this package I have become aware of two other packages that handle application shutdown. They focus on health check signals for process managers like kubernetes. Terminus attaches to an http server instance while lighthouse is purely focused on health signals. It would be possible to integrate lighthouse with graceful-critical for health check signal. I would like to write another post and making any necessary updates to graceful-critical some time soon. Let me know if you would find this helpful.

Catching a Kill Request (SIGTERM and SIGINT)

By default a Nodejs application will handle system calls to terminate by terminating immediately. The code below depicts what happened when a signal is received.

process.on('SIGTERM', () => {
  process.exit(0);
});

By listening to process events we can interrupt the default behavior.

Graceful-Critical Design

The package is synchronous and uses a simple return pattern and a alternative callback pattern for convenience. Both patterns define a critical section by calling a function to enter a critical section and calling another to exit. Entering a critical sections functions much like a mutex or lock. The package tracks the number of executing critical sections. When a system call to terminate is sent to the process, this package waits for all critical sections to exit before exiting the process. Finally when a system call to terminate is received all future enter requests will error. Handling these errors appropriately is important to the successful integration of this package.

Usage

// Basic

async function () {
  let exit;
  try {
    exit = critical.enter();

    /////////////////////
    //CRITICAL SECTION////
    /////////////////////

    exit();
  } catch (err) {
    if (err) // if critical.enter() throws an error the process is shuting down
    if (typeof exit === 'function') exit(); // remember to always exit even if you critical section throws an error.
  }
}

// Callback

critical.enter((err, exit) => {
  if (err) // process is attempting to exit do not enter you critical section

  /////////////////////
  //CRITICAL SECTION////
  /////////////////////

  exit(); // pass this down your callback chain.

Possible Future

This package is simple and I would like to keep it that way. I believe there are improvements that can be made but I would like to live with it for some time before deciding on any improvements. In addition, health signaling would be useful and stats reporting are both features that could be added.

I would greatly appreciate any feedback. If you find this package interesting of useful I would love to hear from you!

Happy Exiting!



comments powered by Disqus