# HTTP shutdown sample
#
# Source pin: Bex `88f0a89792d705a079075c383cf0a5a9561acc06`
# (operator deployment_projection.go: 10s preStop for web pods; workers and
# disk-backed Recreate services skip preStop; maxShutdownDelaySeconds defaults
# to 30s after SIGTERM). This local process does not simulate preStop.

## Run

```bash
npm start
# another terminal:
curl -s localhost:3000/readyz
curl -s 'localhost:3000/work?ms=200'
curl -Ns 'localhost:3000/stream?ticks=3&tickMs=50'
```

## Rehearse graceful shutdown

```bash
npm test
npm run rehearse
```

The rehearsal starts a child server, holds an in-flight `/work` request, sends
SIGTERM, asserts the response completes, checks keep-alive admission during
drain, exercises `/stream`, and forces an overlong hang past
`SHUTDOWN_DEADLINE_MS`. It cleans up only the child it spawned.

## Deploy on Bex

Use `render.yaml` (`type: web`, `healthCheckPath: /readyz`,
`maxShutdownDelaySeconds: 30`). Keep `SHUTDOWN_DEADLINE_MS` below that budget.
Docker `CMD` is exec-form so Node is PID 1.

Local rehearsal is not evidence of live Traefik endpoint propagation or
whole-service zero downtime.

## Endpoints

| Path | Behavior |
| --- | --- |
| `GET /readyz` | 200 while `ready`, 503 while `starting`/`draining` |
| `GET /state` | Machine-readable lifecycle JSON |
| `GET /work?ms=` | Bounded request that waits `ms` then returns JSON |
| `GET /stream?ticks=&tickMs=&hang=1` | SSE stream; `hang=1` never ends until cancel/deadline |

## Signals

First `SIGTERM`/`SIGINT` enters `draining` once: readiness fails, new work gets
503 (including on keep-alive sockets), in-flight work may finish within
`SHUTDOWN_DEADLINE_MS`, then the process exits. Repeated signals are ignored.
