Skip to content

Commit

Permalink
feat: configurable grace period after SIGTERM
Browse files Browse the repository at this point in the history
-q, --grace-period <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
  • Loading branch information
joseluisq committed Feb 1, 2022
1 parent 5f9f9f9 commit 3224261
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 8 deletions.
4 changes: 4 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
14 changes: 10 additions & 4 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down
26 changes: 22 additions & 4 deletions src/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Signals> {
Expand All @@ -12,27 +14,43 @@ pub fn create_signals() -> Result<Signals> {

#[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;
}

0 comments on commit 3224261

Please sign in to comment.