← Recipes

阅读中文版本

Test Graceful Shutdown Signal Handling

Assert that a service stops accepting new work, drains in-flight requests, and exits within the shutdown deadline on SIGTERM.

~30 mintestingreliabilitynodejs

Task

Write an integration test that sends SIGTERM and verifies draining behaviour.

When to use this

Deployments cut off in-flight requests because the process exits immediately on SIGTERM instead of draining.

Prerequisites

  • A service that handles SIGTERM
  • A test harness that can send signals
  • A slow endpoint to observe draining

Steps

1. Start the service and issue a slow request.

const child = spawn('node', ['server.js']);
const slow = fetch('http://localhost:3000/slow');
await new Promise(r => setTimeout(r, 200));

Expected output: A request is in flight when the shutdown begins.

2. Send SIGTERM and assert the request completes.

child.kill('SIGTERM');
const res = await slow;
expect(res.status).toBe(200);
await once(child, 'exit');

Expected output: The in-flight request finishes and the process exits, proving graceful draining.

Troubleshooting

Problem

Process exits before draining

Solution

Register the signal handler early and stop the listener before draining.

Problem

Shutdown hangs forever

Solution

Add a hard deadline timer that forces exit after the grace period.

Limitations

  • Does not cover load-balancer connection draining.
  • Signal semantics differ between platforms.
  • Requires the slow endpoint to expose the behaviour.

What this recipe does

Write an integration test that sends SIGTERM and verifies draining behaviour.

Deployments cut off in-flight requests because the process exits immediately on SIGTERM instead of draining.

Prerequisites

  • A service that handles SIGTERM
  • A test harness that can send signals
  • A slow endpoint to observe draining

Procedure

  1. Start the service and issue a slow request.
const child = spawn('node', ['server.js']);
const slow = fetch('http://localhost:3000/slow');
await new Promise(r => setTimeout(r, 200));

Expected result: A request is in flight when the shutdown begins.

  1. Send SIGTERM and assert the request completes.
child.kill('SIGTERM');
const res = await slow;
expect(res.status).toBe(200);
await once(child, 'exit');

Expected result: The in-flight request finishes and the process exits, proving graceful draining.

How to confirm it worked

Run the commands above and confirm every expected result in order. If a step does not produce the stated output, use the troubleshooting notes before continuing. A passing run shows the in-flight request completes with 200 and the process exits within the configured grace period.

Troubleshooting

Process exits before draining — Register the signal handler early and stop the listener before draining.

Shutdown hangs forever — Add a hard deadline timer that forces exit after the grace period.

Limitations

  • Does not cover load-balancer connection draining.
  • Signal semantics differ between platforms.
  • Requires the slow endpoint to expose the behaviour.