From 322426146c2daeff2d31e760b12494c1d47e2896 Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Tue, 1 Feb 2022 22:25:30 +0100 Subject: [PATCH] feat: configurable grace period after `SIGTERM` -q, --grace-period Defines a grace period in seconds after a `SIGTERM` signal is caught which will delay the server before to shut it down gracefully. 255 seconds maximum [env: SERVER_GRACE_PERIOD=] [default: 0] resolves #79 --- src/config.rs | 4 ++++ src/server.rs | 14 ++++++++++---- src/signals.rs | 26 ++++++++++++++++++++++---- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/config.rs b/src/config.rs index 7d29740..a6bd12c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -154,4 +154,8 @@ pub struct Config { /// It provides The "Basic" HTTP Authentication scheme using credentials as "user-id:password" pairs. Password must be encoded using the "BCrypt" password-hashing function. #[structopt(long, default_value = "", env = "SERVER_BASIC_AUTH")] pub basic_auth: String, + + #[structopt(long, short = "q", default_value = "0", env = "SERVER_GRACE_PERIOD")] + /// Defines a grace period in seconds after a `SIGTERM` signal is caught which will delay the server before to shut it down gracefully. The maximum value is 255 seconds. + pub grace_period: u8, } diff --git a/src/server.rs b/src/server.rs index 8769033..6e826ae 100644 --- a/src/server.rs +++ b/src/server.rs @@ -121,6 +121,10 @@ impl Server { ); let basic_auth = Arc::from(basic_auth); + // Grace period option + let grace_period = opts.grace_period; + tracing::info!("grace period before graceful shutdown: {}s", grace_period); + // Create a service router for Hyper let router_service = RouterService::new(RequestHandler { opts: RequestHandlerOpts { @@ -170,9 +174,10 @@ impl Server { HyperServer::builder(TlsAcceptor::new(tls, incoming)).serve(router_service); #[cfg(not(windows))] - let server = server.with_graceful_shutdown(signals::wait_for_signals(signals)); + let server = + server.with_graceful_shutdown(signals::wait_for_signals(signals, grace_period)); #[cfg(windows)] - let server = server.with_graceful_shutdown(signals::wait_for_ctrl_c()); + let server = server.with_graceful_shutdown(signals::wait_for_ctrl_c(grace_period)); tracing::info!( parent: tracing::info_span!("Server::start_server", ?addr_str, ?threads), @@ -200,9 +205,10 @@ impl Server { .serve(router_service); #[cfg(not(windows))] - let server = server.with_graceful_shutdown(signals::wait_for_signals(signals)); + let server = + server.with_graceful_shutdown(signals::wait_for_signals(signals, grace_period)); #[cfg(windows)] - let server = server.with_graceful_shutdown(signals::wait_for_ctrl_c()); + let server = server.with_graceful_shutdown(signals::wait_for_ctrl_c(grace_period)); tracing::info!( parent: tracing::info_span!("Server::start_server", ?addr_str, ?threads), diff --git a/src/signals.rs b/src/signals.rs index 438ebcf..6fc576d 100644 --- a/src/signals.rs +++ b/src/signals.rs @@ -4,6 +4,8 @@ use { signal_hook_tokio::Signals, }; +use tokio::time::{sleep, Duration}; + #[cfg(not(windows))] /// It creates a common list of signals stream for `SIGTERM`, `SIGINT` and `SIGQUIT` to be observed. pub fn create_signals() -> Result { @@ -12,27 +14,43 @@ pub fn create_signals() -> Result { #[cfg(not(windows))] /// It waits for a specific type of incoming signals included `ctrl+c`. -pub async fn wait_for_signals(signals: Signals) { +pub async fn wait_for_signals(signals: Signals, grace_period_secs: u8) { let mut signals = signals.fuse(); while let Some(signal) = signals.next().await { match signal { SIGHUP => { - // Note: for now we don't do something for SIGHUPs + // NOTE: for now we don't do something for SIGHUPs tracing::debug!("SIGHUP caught, nothing to do about") } SIGTERM | SIGINT | SIGQUIT => { - tracing::debug!("SIGTERM, SIGINT or SIGQUIT signal received, delegating graceful shutdown to the server"); + tracing::info!("SIGTERM, SIGINT or SIGQUIT signal caught"); break; } _ => unreachable!(), } } + // NOTE: once loop above is done then an upstream graceful shutdown should come next. + delay_graceful_shutdown(grace_period_secs).await; + tracing::info!("delegating server's graceful shutdown"); +} + +/// Function intended to delay the server's graceful shutdown providing a grace period in seconds. +async fn delay_graceful_shutdown(grace_period_secs: u8) { + if grace_period_secs > 0 { + tracing::info!( + "grace period of {}s after the SIGTERM started", + grace_period_secs + ); + sleep(Duration::from_secs(grace_period_secs.into())).await; + tracing::info!("grace period has elapsed"); + } } #[cfg(windows)] /// It waits for an incoming `ctrl+c` signal on Windows. -pub async fn wait_for_ctrl_c() { +pub async fn wait_for_ctrl_c(grace_period_secs: u8) { tokio::signal::ctrl_c() .await .expect("failed to install ctrl+c signal handler"); + delay_graceful_shutdown(grace_period_secs).await; } -- libgit2 1.7.2