1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//! Graceful-shutdown signal for the bundled binaries.
//!
//! Watches SIGINT *and* SIGTERM (Ctrl+C only on non-Unix): supervisors like
//! `docker stop` send SIGTERM, and as PID 1 in a `scratch` image the kernel
//! silently drops any signal without an installed handler — so a `ctrl_c()`-only
//! wait never runs cleanup and is SIGKILLed after the stop timeout.
/// Resolves on the first stop signal: SIGINT or SIGTERM on Unix, Ctrl+C
/// elsewhere. Both handlers are armed before the future first suspends, so a
/// signal arriving afterwards is delivered, not missed.
///
/// ```no_run
/// # async fn f(mut handle: whatsapp_rust::bot::BotHandle) {
/// tokio::select! {
/// _ = &mut handle => {}
/// _ = whatsapp_rust::shutdown_signal() => handle.shutdown().await,
/// }
/// # }
/// ```
#[cfg(unix)]
pub async fn shutdown_signal() {
use tokio::signal::unix::{Signal, SignalKind, signal};
// Realistically unreachable for SIGINT/SIGTERM (only uncatchable signals
// fail to register); degrade to whichever installs instead of panicking in
// a top-level shutdown path.
fn install(kind: SignalKind, name: &str) -> Option<Signal> {
signal(kind)
.inspect_err(|e| log::error!("shutdown_signal: cannot watch {name}: {e}"))
.ok()
}
// Registered up front (before the first await) so both are armed by the
// time either can fire.
let mut sigint = install(SignalKind::interrupt(), "SIGINT");
let mut sigterm = install(SignalKind::terminate(), "SIGTERM");
if sigint.is_none() && sigterm.is_none() {
// Neither installed: the future below can never resolve, so graceful
// stop is off. Say so loudly rather than parking silently forever.
log::error!(
"shutdown_signal: no stop signal could be installed; graceful shutdown disabled"
);
}
async fn wait(sig: &mut Option<Signal>) {
match sig {
Some(sig) => {
sig.recv().await;
}
None => std::future::pending::<()>().await,
}
}
tokio::select! {
_ = wait(&mut sigint) => {}
_ = wait(&mut sigterm) => {}
}
}
/// Non-Unix fallback: Ctrl+C is the only portable stop signal Tokio exposes.
#[cfg(not(unix))]
pub async fn shutdown_signal() {
let _ = tokio::signal::ctrl_c().await;
}