static_web_server/
signals.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// This file is part of Static Web Server.
3// See https://static-web-server.net/ for more information
4// Copyright (C) 2019-present Jose Quintana <joseluisq.net>
5
6//! The module provides signals support like `SIGTERM`, `SIGINT` and `SIGQUIT`.
7//!
8
9use std::sync::Arc;
10use tokio::sync::{watch::Receiver, Mutex};
11use tokio::time::{sleep, Duration};
12
13#[cfg(unix)]
14use {
15    crate::Result, futures_util::stream::StreamExt, signal_hook::consts::signal::*,
16    signal_hook_tokio::Signals,
17};
18
19#[cfg(unix)]
20#[cfg_attr(docsrs, doc(cfg(unix)))]
21#[inline]
22/// It creates a common list of signals stream for `SIGTERM`, `SIGINT` and `SIGQUIT` to be observed.
23pub fn create_signals() -> Result<Signals> {
24    Ok(Signals::new([SIGHUP, SIGTERM, SIGINT, SIGQUIT])?)
25}
26
27#[cfg(unix)]
28/// It waits for a specific type of incoming signals included `ctrl+c`.
29pub async fn wait_for_signals(
30    signals: Signals,
31    grace_period_secs: u8,
32    cancel_recv: Arc<Mutex<Option<Receiver<()>>>>,
33) {
34    let (first_tx, mut base_rx) = tokio::sync::mpsc::channel(1);
35    let last_tx = first_tx.clone();
36
37    tokio::spawn(async move {
38        let mut signals = signals.fuse();
39        while let Some(signal) = signals.next().await {
40            match signal {
41                SIGHUP => {
42                    // NOTE: for now we don't do something for SIGHUPs
43                    tracing::debug!("SIGHUP caught, nothing to do about")
44                }
45                SIGTERM | SIGINT | SIGQUIT => {
46                    tracing::info!("SIGTERM, SIGINT or SIGQUIT signal caught");
47                    first_tx.send(()).await.ok();
48                    break;
49                }
50                _ => unreachable!(),
51            }
52        }
53    });
54
55    tokio::spawn(async move {
56        if let Some(recv) = &mut *cancel_recv.lock().await {
57            recv.changed().await.ok();
58            last_tx.send(()).await.ok();
59            tracing::info!("signals interrupted manually by cancel_recv");
60        }
61    });
62
63    base_rx.recv().await.take();
64
65    // NOTE: once loop above is done then an upstream graceful shutdown should come next.
66    delay_graceful_shutdown(grace_period_secs).await;
67    tracing::info!("delegating server's graceful shutdown");
68}
69
70/// Function intended to delay the server's graceful shutdown providing a grace period in seconds.
71async fn delay_graceful_shutdown(grace_period_secs: u8) {
72    if grace_period_secs > 0 {
73        tracing::info!(
74            "grace period of {}s after the SIGTERM started",
75            grace_period_secs
76        );
77        sleep(Duration::from_secs(grace_period_secs.into())).await;
78        tracing::info!("grace period has elapsed");
79    }
80}
81
82#[cfg(windows)]
83#[cfg_attr(docsrs, doc(cfg(windows)))]
84/// It waits for an incoming `ctrl+c` signal on Windows.
85pub async fn wait_for_ctrl_c(cancel_recv: Arc<Mutex<Option<Receiver<()>>>>, grace_period_secs: u8) {
86    if let Some(receiver) = &mut *cancel_recv.lock().await {
87        receiver.changed().await.ok();
88    }
89
90    delay_graceful_shutdown(grace_period_secs).await;
91    tracing::info!("delegating server's graceful shutdown");
92}