Arresto Grazioso

L'arresto grazioso (Graceful Shutdown) si riferisce al processo in cui, durante lo spegnimento di un server, non vengono immediatamente terminate tutte le connessioni. Invece, il server interrompe prima l'accettazione di nuove richieste, concedendo alle richieste esistenti il tempo necessario per completare l'elaborazione prima di chiudere il servizio. Questo approccio evita l'interruzione brusca delle richieste, migliorando così l'esperienza utente e l'affidabilità del sistema.

Salvo fornisce supporto per l'arresto grazioso tramite il metodo handle del Server, che recupera l'handle del server, seguito dalla chiamata al metodo stop_graceful per implementare lo spegnimento. Dopo aver invocato questo metodo, il server:

  • Interrompe l'accettazione di nuove richieste di connessione
  • Attende il completamento dell'elaborazione delle richieste esistenti
  • Chiude forzatamente eventuali connessioni rimanenti dopo un timeout specificato (se fornito)

Ecco un semplice esempio:

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);
}

Nell'esempio sopra:

  • server.handle() recupera l'handle del server, che può essere utilizzato per controllare il ciclo di vita del server
  • handle.stop_graceful(None) avvia il processo di arresto grazioso, dove None indica che non è impostato alcun timeout, il che significa che il server attenderà indefinitamente il completamento di tutte le richieste
  • Per impostare un timeout, è possibile passare Some(Duration), dopo il quale eventuali connessioni rimanenti verranno chiuse forzatamente

Questo approccio è particolarmente adatto per applicazioni distribuite in ambienti containerizzati o su piattaforme cloud, nonché per scenari che richiedono aggiornamenti a caldo per garantire che le richieste non vengano interrotte inaspettatamente.