← Índice

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 tq para este capítulo está en 8-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.