8.5 Antes de que la puerta se cierre
Esta traducción fue asistida por IA y revisada superficialmente. Si encuentras errores o algo no suena natural, escríbeme — lo agradezco.
El libro de cuentas ahora lo muestra todo. Cada petición deja un registro: qué llegó, qué
se devolvió, cuándo y por qué. Hay una cosa que no puede registrarse: el momento en que el
libro deja de estar. Ctrl+C no deja ninguna entrada. El proceso desaparece - y en el
libro, lo que hubo antes de él. Un final sin marca.
Lo que necesitas
Ctrl+C envía al proceso una señal de terminación. Por defecto el proceso se detiene de
inmediato - independientemente de lo que estuviera ocurriendo dentro. Una petición a mitad
de procesamiento se corta en seco: el cliente no recibe respuesta, una escritura en disco
puede quedar incompleta.
El cierre ordenado (graceful shutdown) aborda esto en tres pasos: dejar de aceptar nuevas conexiones, esperar a que terminen las peticiones en curso, salir. Axum lo soporta directamente - solo hay que describir en qué momento exacto debe detenerse el servidor.
Reproduciendo el problema
Para ver el problema se necesita una petición que dure lo suficiente. Añade un handler a
crates/api/src/routes.rs que espera intencionadamente 30 segundos:
use std::time::Duration;
use tokio::time::sleep;
async fn slow() -> &'static str {
sleep(Duration::from_secs(30)).await;
"done\n"
}
Y en la función router - la ruta:
.route("/slow", get(slow))
Abre dos terminales. En el primero - arranca el servidor:
$ make serve
2026-06-25T10:00:00Z INFO tq_api: listening on 0.0.0.0:3000
En el segundo - envía una petición:
$ curl localhost:3000/slow
Vuelve al primer terminal y pulsa Ctrl+C. En el segundo:
curl: (52) Empty reply from server
El servidor desaparece. La conexión cae. La petición se esfuma sin dejar rastro.
La construcción
En crates/api/src/main.rs - una nueva función de señal:
async fn shutdown_signal() {
tokio::signal::ctrl_c().await.unwrap();
tracing::info!("shutdown signal received");
}
Y al final de main, sustituyendo axum::serve(listener, app).await.unwrap():
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await
.unwrap();
tracing::info!("server stopped");
tokio::signal::ctrl_c() espera Ctrl+C y completa exactamente una vez.
.with_graceful_shutdown(shutdown_signal()) le dice al servidor: cuando shutdown_signal()
complete - deja de aceptar nuevas conexiones, espera a que terminen las peticiones actuales
y solo entonces sal. La línea tracing::info!("server stopped") se ejecuta cuando el
servidor se ha detenido de verdad - no antes.
El resultado
Repite el escenario de Reproduciendo el problema: make serve en una ventana,
curl localhost:3000/slow en la segunda, Ctrl+C en la primera. Ahora en la segunda
ventana - no una conexión caída, sino una respuesta:
$ curl localhost:3000/slow
done
Y en la primera ventana el log muestra el camino completo hasta el final:
2026-06-25T10:00:00Z INFO tq_api: listening on 0.0.0.0:3000
^C
2026-06-25T10:00:05Z INFO tq_api: shutdown signal received
2026-06-25T10:00:35Z INFO tq_api: server stopped
El libro de cuentas tiene ahora una marca para el final. make ci pasa.
El código completo de
tqpara este capítulo está en8-ready/05-before-the-door-closes/.
Lore: Señales
Ctrl+C no es solo una combinación de teclas. El terminal la convierte en SIGINT - una
señal del sistema operativo enviada al proceso. Sin gestionarla, el proceso termina de
inmediato.
tokio::signal::ctrl_c() intercepta esta señal - en lugar de morir de inmediato, el
proceso la espera a través de .await y decide por sí mismo qué hacer a continuación.
with_graceful_shutdown aprovecha esa oportunidad: termina de leer, termina de escribir,
cierra las conexiones.
Lore: Cuándo importa
/slow es un ejemplo sintético: las peticiones reales tardan milisegundos, y atrapar una
en el momento del cierre solo es difícil. Bajo carga elevada esto deja de ser raro - a mil
peticiones por segundo, varias están siempre en curso. Sin cierre ordenado, cada reinicio
descarta algunas de ellas.