Your app is dying loudly: the missing shutdown handler
Por Seaworthy · 14 de octubre de 2026
Photo by Kevin Ache on Unsplash
Your app is dying loudly: the missing shutdown handler
I keep seeing the same defect in code generated by AI assistants, in tutorials, and in small services that ship to production. The process starts, listens on a port, and never plans for its own death. No signal handling. No draining. No closing of resources. The app just stops when the operating system says so.
That feels wrong. A process that can be killed at any instant without cleanup is not a finished service. It is a demo that happens to accept traffic.
What actually happens without a shutdown handler
On Linux, SIGTERM is the polite request to stop. SIGKILL is the boot. Most container runtimes send SIGTERM, wait a short grace period (often 10 seconds), then send SIGKILL. If your program ignores SIGTERM, it has no chance to finish in-flight requests, flush logs, close database connections, or release locks.
Here is the pattern I see in AI-generated Node servers all the time:
// The common, broken pattern
const http = require('http');
const server = http.createServer(app);
server.listen(3000);
// No signal handling anywhere in the file
That server will accept requests until the kernel tears it down. A client in the middle of a POST gets a connection reset. A payment webhook may or may not have been recorded. A transaction may be open on the database side. Nobody wrote a shutdown handler because the happy path worked locally.
The damage is not only dropped requests
Dropped requests are the visible symptom. The deeper problems are operational and security-adjacent.
First, resource leaks. Open file descriptors, sockets, and database sessions stay open until the OS reclaims them. In a long-running deploy with rolling restarts, that can exhaust connection pools on shared databases. The next service to start fails to connect, and you spend an afternoon blaming the network.
Second, locks and leases. Distributed systems often use advisory locks or leader election. If the process dies without releasing them, other replicas wait for a timeout. That timeout can be minutes. During those minutes, your system is partially unavailable even though the code is "running."
Third, log loss. Buffered logs and metrics are gone. Security teams care about this more than developers do. An audit trail with a gap around every deploy is not much of an audit trail. If you cannot show what happened during a restart, you cannot investigate an incident that started during one.
Fourth, and this one bothers me most, it hides bugs. When shutdown is abrupt, state is discarded. A half-written file or a partially committed batch might never be noticed because the next deploy wipes the evidence. Graceful shutdown forces you to define what "finished" means. That definition is worth having.
What a sane shutdown handler looks like
The fix is not complicated. It is a few lines that most AI assistants will happily generate if you ask for them by name.
const http = require('http');
const server = http.createServer(app);
let shuttingDown = false;
function shutdown(signal) {
if (shuttingDown) return;
shuttingDown = true;
console.log(`received ${signal}, draining`);
server.close(() => {
// close database pool, flush logs, release locks here
process.exit(0);
});
// Force exit if draining takes too long
setTimeout(() => process.exit(1), 8000).unref();
}
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
server.listen(3000);
Two details matter. server.close() stops accepting new connections and waits for existing ones to finish. The timer is a backstop so a stuck request cannot hold the process forever. You should set that timer below your platform's grace period. If Kubernetes waits 10 seconds, exit at 8.
For Python, the shape is similar: catch signal.SIGTERM, set a flag, stop accepting work, join worker threads or async tasks, close pools, then exit. For Go, use signal.NotifyContext and pass the context into your HTTP server's Shutdown method. The language changes. The idea does not.
Why AI assistants skip this
I think the reason is training data. Most code snippets are written to show an idea, not to run for months. Tutorials stop at "it works." Generated code imitates that. Unless you ask for signal handling, connection draining, and cleanup, you get the shortest path to a green test.
So ask. Put it in your prompt: "Handle SIGTERM and SIGINT, drain in-flight requests, close the database pool, and exit non-zero on timeout." Then read the result. Assistants will produce the pattern once you name it.
Make it part of your definition of done
A service is not done when it starts. It is done when it can stop without lying to its clients, its database, or its logs. Add a shutdown handler to every long-running process you own. Test it by sending SIGTERM in a staging environment and watching the logs. If the process vanishes with no message, you have work to do.
I would rather see a boring shutdown log line than a mysterious gap in production. Boring is good here.
Seaworthy builds tools for developers who want to catch these gaps before they reach production.
Este artículo fue generado por IA y resume fuentes públicamente disponibles.
Seaworthy analiza tu repositorio en busca de los problemas que se tratan en artículos como este.
Brechas de seguridad, secretos expuestos y configuraciones erróneas detectados antes de desplegar. Gratis, sin cuenta necesaria.