Apagado Elegante

El apagado elegante se refiere al proceso en el que, al apagar un servidor, no se terminan inmediatamente todas las conexiones. En su lugar, primero deja de aceptar nuevas solicitudes mientras permite que las solicitudes existentes tengan tiempo suficiente para completar su procesamiento antes de cerrar el servicio. Este enfoque evita que las solicitudes se interrumpan abruptamente, mejorando así la experiencia del usuario y la confiabilidad del sistema.

Salvo proporciona soporte para el apagado elegante a través del método handle del Server, que obtiene el controlador del servidor, seguido de llamar al método stop_graceful para implementar el apagado. Después de invocar este método, el servidor:

  • Dejará de aceptar nuevas solicitudes de conexión
  • Esperará a que las solicitudes existentes completen su procesamiento
  • Cerrará forzosamente cualquier conexión restante después de un tiempo de espera especificado (si se proporciona)

Aquí hay un ejemplo simple:

main.rs
Cargo.toml
use salvo::prelude::*;
use salvo::server::ServerHandle;
use tokio::signal;

#[tokio::main]
async fn main() {
    // Bind server to port 8698
    let acceptor = TcpListener::new("127.0.0.1:8698").bind().await;
    // Create server instance
    let server = Server::new(acceptor);
    // Get server handle for graceful shutdown
    let handle = server.handle();

    // Listen Shutdown Signal
    tokio::spawn(listen_shutdown_signal(handle));

    // Start serving requests (empty router in this example)
    server.serve(Router::new()).await;
}

async fn listen_shutdown_signal(handle: ServerHandle) {
    // Wait Shutdown Signal
    let ctrl_c = async {
        // Handle Ctrl+C signal
        signal::ctrl_c()
            .await
            .expect("failed to install Ctrl+C handler");
    };

    #[cfg(unix)]
    let terminate = async {
        // Handle SIGTERM on Unix systems
        signal::unix::signal(signal::unix::SignalKind::terminate())
            .expect("failed to install signal handler")
            .recv()
            .await;
    };

    #[cfg(windows)]
    let terminate = async {
        // Handle Ctrl+C on Windows (alternative implementation)
        signal::windows::ctrl_c()
            .expect("failed to install signal handler")
            .recv()
            .await;
    };

    // Wait for either signal to be received
    tokio::select! {
        _ = ctrl_c => println!("ctrl_c signal received"),
        _ = terminate => println!("terminate signal received"),
    };

    // Graceful Shutdown Server
    handle.stop_graceful(None);
}

En el ejemplo anterior:

  • server.handle() obtiene el controlador del servidor, que puede usarse para controlar el ciclo de vida del servidor
  • handle.stop_graceful(None) inicia el proceso de apagado elegante, donde None indica que no se establece un tiempo de espera, lo que significa que el servidor esperará indefinidamente a que todas las solicitudes se completen
  • Para establecer un tiempo de espera, puede pasar Some(Duration), después del cual cualquier conexión restante se cerrará forzosamente

Este enfoque es especialmente adecuado para aplicaciones implementadas en entornos de contenedores o en plataformas en la nube, así como para escenarios que requieren actualizaciones en caliente para garantizar que las solicitudes no se interrumpan inesperadamente.