From ac2638dd9a7851ccf9e12e3b47147edc6d081320 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 13 Sep 2025 09:43:25 -0500 Subject: [PATCH] feat: implement proper SIGTERM handling for container shutdown --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/main.rs | 51 +++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ec3637..23ea398 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -168,7 +168,7 @@ dependencies = [ [[package]] name = "banner" -version = "0.2.0" +version = "0.2.1" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 5122260..0141ee3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "banner" -version = "0.2.0" +version = "0.2.1" edition = "2024" default-run = "banner" diff --git a/src/main.rs b/src/main.rs index c7a1e06..ba50809 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use figment::value::UncasedStr; use num_format::{Locale, ToFormattedString}; -use serenity::all::{ActivityData, ClientBuilder, GatewayIntents}; +use serenity::all::{ActivityData, ClientBuilder, Context, GatewayIntents}; use tokio::signal; use tracing::{error, info, warn}; use tracing_subscriber::{EnvFilter, FmtSubscriber}; @@ -28,10 +28,7 @@ mod services; mod state; mod web; -async fn update_bot_status( - ctx: &serenity::all::Context, - app_state: &AppState, -) -> Result<(), anyhow::Error> { +async fn update_bot_status(ctx: &Context, app_state: &AppState) -> Result<(), anyhow::Error> { let course_count = app_state.get_course_count().await?; ctx.set_activity(Some(ActivityData::playing(format!( @@ -239,7 +236,7 @@ async fn main() { // Spawn all registered services service_manager.spawn_all(); - // Set up CTRL+C signal handling + // Set up signal handling for both SIGINT (Ctrl+C) and SIGTERM let ctrl_c = async { signal::ctrl_c() .await @@ -247,7 +244,23 @@ async fn main() { info!("received ctrl+c, gracefully shutting down..."); }; - // Main application loop - wait for services or CTRL+C + #[cfg(unix)] + let sigterm = async { + use tokio::signal::unix::{SignalKind, signal}; + let mut sigterm_stream = + signal(SignalKind::terminate()).expect("Failed to install SIGTERM signal handler"); + sigterm_stream.recv().await; + info!("received SIGTERM, gracefully shutting down..."); + }; + + #[cfg(not(unix))] + let sigterm = async { + // On non-Unix systems, create a future that never completes + // This ensures the select! macro works correctly + std::future::pending::<()>().await; + }; + + // Main application loop - wait for services or signals let mut exit_code = 0; tokio::select! { @@ -289,7 +302,7 @@ async fn main() { } } _ = ctrl_c => { - // User requested shutdown + // User requested shutdown via Ctrl+C info!("user requested shutdown via ctrl+c"); match service_manager.shutdown(shutdown_timeout).await { Ok(elapsed) => { @@ -310,6 +323,28 @@ async fn main() { } } } + _ = sigterm => { + // System requested shutdown via SIGTERM + info!("system requested shutdown via SIGTERM"); + match service_manager.shutdown(shutdown_timeout).await { + Ok(elapsed) => { + info!( + remaining = format!("{:.2?}", shutdown_timeout - elapsed), + "graceful shutdown complete" + ); + info!("graceful shutdown complete"); + } + Err(pending_services) => { + warn!( + pending_count = pending_services.len(), + pending_services = ?pending_services, + "graceful shutdown elapsed - {} service(s) did not complete", + pending_services.len() + ); + exit_code = 2; + } + } + } } info!(exit_code, "application shutdown complete");