Graceful Shutdown

Graceful Shutdown bezeichnet den Prozess, bei dem ein Server beim Herunterfahren nicht sofort alle Verbindungen beendet. Stattdessen stoppt er zunächst die Annahme neuer Anfragen und gewährt bestehenden Anfragen ausreichend Zeit, ihre Verarbeitung abzuschließen, bevor der Dienst geschlossen wird. Dieser Ansatz verhindert ein abruptes Abbrechen von Anfragen und verbessert dadurch die Benutzererfahrung und Systemzuverlässigkeit.

Salvo unterstützt Graceful Shutdown über die handle-Methode des Server, die den Server-Handle abruft, gefolgt vom Aufruf der stop_graceful-Methode zur Implementierung des Herunterfahrens. Nach dem Aufruf dieser Methode führt der Server folgende Schritte aus:

  • Stoppt die Annahme neuer Verbindungsanfragen
  • Wartet auf den Abschluss der Verarbeitung bestehender Anfragen
  • Erzwingt das Schließen verbleibender Verbindungen nach einem festgelegten Timeout (falls angegeben)

Hier ein einfaches Beispiel:

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

Im obigen Beispiel:

  • server.handle() ruft den Server-Handle ab, der zur Steuerung des Server-Lebenszyklus verwendet werden kann
  • handle.stop_graceful(None) initiiert den Graceful-Shutdown-Prozess, wobei None angibt, dass kein Timeout gesetzt ist – der Server wartet unbegrenzt auf den Abschluss aller Anfragen
  • Um ein Timeout zu setzen, kann Some(Duration) übergeben werden; danach werden verbleibende Verbindungen erzwungen geschlossen

Dieser Ansatz eignet sich besonders für Anwendungen in Container-Umgebungen oder auf Cloud-Plattformen sowie für Szenarien mit Hot Updates, um sicherzustellen, dass Anfragen nicht unerwartet unterbrochen werden.