Graceful shutdown

Before the code: what graceful shutdown actually means

Picture a restaurant at closing time. The good waiter does not yell stop and dump plates on the floor. They stop seating new guests, let the people already eating finish, then turn the lights off. That is graceful shutdown for a server. When the operating system sends a stop signal, you want the server to stop accepting new connections, let the writes already in flight finish, then quit. In Node you wire this with process.on(SIGTERM), server.close, and a small drain loop.

Stop accepting, drain, exit

A clean shutdown has three phases: stop accepting new work, let in-flight work finish, then exit the process.

Quiz: Quiz

Loading practice…

Same idea as Go's context-based shutdown, simpler API. server.close stops listening. Existing conns keep their handlers. A setInterval polls for drain.

09-graceful-shutdown/server.mjs
javascript
const conns = new Set();

const server = createServer((conn) => {
  conns.add(conn);
  // ... data handler unchanged ...
  conn.on("close", () => conns.delete(conn));
  conn.on("error", () => {});
});

Every live socket is tracked in a module-level Set: add on connect, delete on close. The shutdown sequence below reads conns.size to know when the last client has left.

09-graceful-shutdown/server.mjs
javascript
function shutdown(signal) {
  if (shuttingDown) return;
  shuttingDown = true;
  console.log(`\n  [shutdown] ${signal} received, draining...`);

  server.close(() => console.log("  [shutdown] listener closed"));

  const deadline = setTimeout(() => {
    console.log("  [shutdown] forced after 5s");
    for (const c of conns) c.destroy();
    process.exit(0);
  }, 5000);
  deadline.unref();

  const checkDrain = setInterval(() => {
    if (conns.size === 0) {
      clearInterval(checkDrain);
      console.log("  [shutdown] clean drain");
      process.exit(0);
    }
  }, 100);
  checkDrain.unref();
}

process.on("SIGINT", () => shutdown("SIGINT"));
process.on("SIGTERM", () => shutdown("SIGTERM"));

The whole shutdown sequence. setInterval polls for drain, setTimeout caps the wait.

After 5 seconds without natural drain, we call conn.destroy() on every remaining conn (brutal close). Then process.exit. This is the production pattern Kubernetes expects.