Priority
P1
Problem
The shutdown signal callback returns an unobserved Promise, so timeout failures become unhandled rejections; separately, the Node listen path does not bind its server close lifecycle to graceful shutdown. A terminated process can retain its listening socket.
Evidence
packages/framework-context/src/libs/ShutdownManager.ts registers async handleSignal directly with process.on, while its timeout path rejects. packages/transports-http/src/libs/CrocoApp.ts starts the server without a shutdown owner; GracefulShutdownMiddleware.ts drains requests but does not close that server.
Desired outcome
A signal produces one observable shutdown outcome, stops accepting connections, drains bounded work, closes the Node listener, and exits according to a documented failure policy.
Implementation path
- Wrap signal shutdown with explicit catch/log/exit policy.
- Make
listen() expose or own a closeable server lifecycle.
- Order reject-new-work, drain, close, hooks, and telemetry flush deterministically.
Acceptance criteria
- A hanging hook does not create an unhandled rejection.
- SIGTERM closes the listener after the configured drain.
- Repeated signals are idempotent.
Validation
- Add real Node socket tests for SIGTERM, keep-alive drain, timeout, and listener close.
- Run
pnpm --filter @croco/framework-context test and pnpm --filter @croco/transports-http test.
Scope
Does not change serverless shutdown semantics.
Priority
P1
Problem
The shutdown signal callback returns an unobserved Promise, so timeout failures become unhandled rejections; separately, the Node listen path does not bind its server close lifecycle to graceful shutdown. A terminated process can retain its listening socket.
Evidence
packages/framework-context/src/libs/ShutdownManager.tsregisters asynchandleSignaldirectly withprocess.on, while its timeout path rejects.packages/transports-http/src/libs/CrocoApp.tsstarts the server without a shutdown owner;GracefulShutdownMiddleware.tsdrains requests but does not close that server.Desired outcome
A signal produces one observable shutdown outcome, stops accepting connections, drains bounded work, closes the Node listener, and exits according to a documented failure policy.
Implementation path
listen()expose or own a closeable server lifecycle.Acceptance criteria
Validation
pnpm --filter @croco/framework-context testandpnpm --filter @croco/transports-http test.Scope
Does not change serverless shutdown semantics.