Shutdown Check

Search documentation

Find a page or section

Follow a check from the free-port probe to the final process and port checks.

shutdown-check treats your service like a deployment platform does. It starts the real command, sends HTTP traffic, delivers SIGTERM, and observes the result from outside the process. It does not import your application or call the shutdown handler directly.

What is a black-box shutdown test?

A black-box shutdown test uses only the service's public behavior: its command, port, HTTP routes, signal handling, and exit status. This covers problems that unit tests often miss, including shell wrappers, framework behavior, real sockets, process exit, and child processes.

The tradeoff is visibility. shutdown-check can see HTTP responses and process events, but it cannot know whether an internal database write or queue message finished unless the response proves it. See compatibility and limits for the exact boundary.

What happens during a run?

The stages always run in this order:

StageWhat shutdown-check doesMain failures
1Confirms the port is freeSC001
2Starts the configured commandSC002
3Waits for readinessSC100–101
4Opens work and proves it is activeSC110–111
5Confirms the process and work are still aliveSC112
6Sends SIGTERMSC113
7Optionally repeats the signalSC114
8Optionally checks readiness and new-request rejectionSC310–312
9Waits for active responsesSC200–203
10Checks the process exitSC300–301
11Confirms the port is closedSC302

The first reported failure identifies the earliest useful problem after all required waits have settled. A pass is SC000.

1. Check that the port is free

Before launch, shutdown-check sends a short request to the readiness URL. The connection must be refused. Any response, timeout, or unclear network error means the port is not confirmed free and produces SC001.

This prevents a false pass against a server that was already running. It also makes parallel tests predictable: each test needs its own port.

2. Launch the real command

The command array is spawned directly without a shell. It runs in its own process group, with cwd and env from the config.

shutdown-check.json
{
  "command": ["node", "dist/server.js"],
  "cwd": ".",
  "env": { "PORT": "3510", "NODE_ENV": "production" }
}

Use the same entry point you use in production when possible. Starting the server directly also avoids wrappers that swallow SIGTERM or exit before a child server.

3. Wait for readiness

shutdown-check polls baseUrl + readiness.path until it receives the expected status. Refused connections and other statuses are retried until readiness.timeoutMs.

  • The process stays alive but never becomes ready: SC100.
  • The process exits or cannot start: SC101.

No workload is sent until readiness passes. This separates startup failures from shutdown failures.

4. Prove that work is active

The test sends one or more workload requests, then waits for the configured start barrier.

Response-headers barrier

Every request must receive response headers while its body is still open. Use this with streaming routes or controlled endpoints that flush headers before finishing.

Probe barrier

A separate route must change from inactiveStatus to activeStatus while the workload request remains open. Use this when the handler sends its response only after all work completes.

The check reports SC110 when the probe is already active, or SC111 when work cannot be confirmed. The in-flight work guide includes complete examples.

5. Recheck the process and requests

There is a small gap between the barrier passing and the signal being sent. shutdown-check verifies that the service and every workload request are still active in that gap. If either ended, the result is SC112.

This prevents a test from passing with a workload that was only barely slow enough to reach the barrier.

6. Send SIGTERM

shutdown-check sends SIGTERM to the exact process it launched. It does not signal the whole process group at this stage. That detail exposes wrappers: the launcher must forward the signal to the real server.

If the operating system refuses the signal, the result is SC113. On success, the shutdown deadline begins and the timeline records signal sent.

7. Repeat the signal when requested

When repeatSignalAfterMs is set, shutdown-check sends another SIGTERM only if the process and original requests are still active. This checks that a second signal does not force an early exit.

If the repeated signal cannot be tested or delivered, the result is SC114. Keep the repeat delay shorter than both the workload duration and shutdown.deadlineMs.

8. Check traffic draining

These checks are optional.

With readinessWithdrawal: true, shutdown-check polls the readiness route after the signal. A non-ready status or refused connection counts as withdrawn. A timeout does not. If readiness never changes, the result is SC310.

With newRequests, the test then sends one new GET while the original work is still active. The request must return a configured rejection status or be refused when that behavior is allowed.

  • New work is accepted or times out: SC311.
  • Original work ends before the rejection test: SC312.

See readiness and draining for a server that returns 503 during this window.

9. Wait for each active response

Every workload request must finish before shutdown.deadlineMs with the configured status and optional body text.

ResultCode
The request never endsSC200
The connection is reset or closed earlySC201
The final status differs from workload.statusSC202
The body lacks workload.bodyIncludesSC203

Concurrent requests are checked individually. The first failing request in request order supplies the code.

10. Check process exit

The service must exit before the same shutdown deadline.

  • It is still running: SC300.
  • Its code differs from shutdown.exitCode, or a signal ended it: SC301.

The default expected code is 0. A planned shutdown should normally look like a clean exit to process managers and monitoring.

11. Confirm that the port closed

After the launched process exits, shutdown-check tries the readiness URL once more. The connection must be refused. If something still answers, a child server is probably still running and the result is SC302.

This is how the test catches npm start, shell scripts, or other launchers that exit without stopping the actual server.

How are failures cleaned up?

After any result, shutdown-check closes its open requests and clears timers. If the service or a child process remains, the tool sends SIGKILL to the process group it created. Cleanup prevents a failed test from leaving the port occupied for the next run.

Cleanup is not a passing shutdown. A service passes only when it finishes its own work, exits with the expected code, and closes the port before cleanup is needed.

How should I use the timeline?

Read it from top to bottom and find the last successful event before check failed. For example:

  • no service ready means startup or readiness failed;
  • no work confirmed active means the start barrier failed;
  • signal sent followed by no request completion means the drain hung;
  • process exited while the port stays open means a child server survived.

The output reference lists every event and the fields available in JSON.