グレースフルシャットダウン

グレースフルシャットダウンとは、サーバーを停止する際に即座にすべての接続を切断するのではなく、まず新規リクエストの受付を停止し、既に受け付けたリクエストが十分な時間をかけて処理を完了できるようにしてからサービスを終了する方式です。この方法によりリクエストが突然中断されることを防ぎ、ユーザーエクスペリエンスとシステムの信頼性を向上させることができます。

Salvoはグレースフルシャットダウンをサポートしており、Serverhandleメソッドでサーバーハンドルを取得し、stop_gracefulメソッドを呼び出すことで実現できます。このメソッドを呼び出すと、サーバーは以下の動作を行います:

  • 新規接続リクエストの受付を停止
  • 既存のリクエスト処理が完了するまで待機
  • 指定されたタイムアウト時間後(指定されている場合)、残りの接続を強制終了

以下は簡単な例です:

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

上記の例では:

  • server.handle()でサーバーのハンドルを取得し、サーバーのライフサイクル制御に使用可能
  • handle.stop_graceful(None)でグレースフルシャットダウン処理を開始。Noneはタイムアウト時間を設定せず、すべてのリクエスト処理が完了するまで待機することを意味します
  • タイムアウト時間を設定する場合はSome(Duration)を渡すことができ、タイムアウト後は残りの接続を強制終了します

この方式は特に、コンテナ環境やクラウドプラットフォームにデプロイするアプリケーションや、ホットアップデート時にリクエストが予期せず中断されないようにする必要がある場合に適しています。