Graceful Shutdown

Graceful shutdown refers to the process where, when a server is being shut down, it does not immediately terminate all connections. Instead, it first stops accepting new requests while allowing existing requests sufficient time to complete their processing before closing the service. This approach prevents requests from being abruptly interrupted, thereby improving user experience and system reliability.

Salvo provides support for graceful shutdown through the handle method of the Server, which retrieves the server handle, followed by calling the stop_graceful method to implement the shutdown. After invoking this method, the server will:

  • Stop accepting new connection requests
  • Wait for existing requests to complete processing
  • Forcefully close any remaining connections after a specified timeout (if provided)

Here is a simple example:

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

In the example above:

  • server.handle() retrieves the server handle, which can be used to control the server's lifecycle
  • handle.stop_graceful(None) initiates the graceful shutdown process, where None indicates no timeout is set, meaning the server will wait indefinitely for all requests to complete
  • To set a timeout, you can pass Some(Duration), after which any remaining connections will be forcefully closed

This approach is particularly suitable for applications deployed in container environments or on cloud platforms, as well as for scenarios requiring hot updates to ensure that requests are not unexpectedly interrupted.