torrust_server_lib/
signals.rs1use derive_more::Display;
3use tokio_util::sync::CancellationToken;
4use torrust_net_primitives::service_binding::ServiceBinding;
5use tracing::instrument;
6
7#[derive(Debug)]
11pub struct Started {
12 pub service_binding: ServiceBinding,
13 pub address: std::net::SocketAddr,
14}
15
16#[derive(Copy, Clone, Debug, Display)]
20pub enum Halted {
21 Normal,
22}
23
24#[instrument(skip(cancellation_token))]
29pub async fn cancellation_signal(cancellation_token: CancellationToken) {
30 cancellation_token.cancelled().await;
31}
32
33#[instrument(skip())]
39pub async fn global_shutdown_signal() {
40 let ctrl_c = async {
41 tokio::signal::ctrl_c().await.expect("failed to install Ctrl+C handler");
42 };
43
44 #[cfg(unix)]
45 let terminate = async {
46 tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
47 .expect("failed to install signal handler")
48 .recv()
49 .await;
50 };
51
52 #[cfg(not(unix))]
53 let terminate = std::future::pending::<()>();
54
55 tokio::select! {
56 () = ctrl_c => {tracing::warn!("caught interrupt signal (ctrl-c), halting...");},
57 () = terminate => {tracing::warn!("caught interrupt signal (terminate), halting...");}
58 }
59}
60
61#[instrument(skip(rx_halt))]
67pub async fn shutdown_signal(rx_halt: tokio::sync::oneshot::Receiver<Halted>) {
68 let halt = async {
69 match rx_halt.await {
70 Ok(signal) => signal,
71 Err(err) => panic!("Failed to install stop signal: {err}"),
72 }
73 };
74
75 tokio::select! {
76 signal = halt => { tracing::debug!("Halt signal processed: {}", signal) },
77 () = global_shutdown_signal() => { tracing::debug!("Global shutdown signal processed") }
78 }
79}
80
81#[instrument(skip(rx_halt))]
83pub async fn shutdown_signal_with_message(rx_halt: tokio::sync::oneshot::Receiver<Halted>, message: String) {
84 shutdown_signal(rx_halt).await;
85
86 tracing::info!("{message}");
87}
88
89#[cfg(test)]
90mod tests {
91 use tokio::sync::oneshot;
92 use tokio_util::sync::CancellationToken;
93
94 use super::{Halted, cancellation_signal, shutdown_signal};
95
96 #[tokio::test]
97 async fn cancellation_signal_should_resolve_when_the_token_is_cancelled() {
98 let cancellation_token = CancellationToken::new();
99 let wait_task = tokio::spawn(cancellation_signal(cancellation_token.clone()));
100
101 tokio::task::yield_now().await;
102
103 assert!(!wait_task.is_finished());
104
105 cancellation_token.cancel();
106
107 wait_task.await.expect("cancellation wait task should complete");
108 }
109
110 #[tokio::test]
111 async fn shutdown_signal_should_resolve_when_it_receives_a_legacy_halted_message() {
112 let (halt_sender, halt_receiver) = oneshot::channel();
113 let wait_task = tokio::spawn(shutdown_signal(halt_receiver));
114
115 halt_sender
116 .send(Halted::Normal)
117 .expect("legacy halt receiver should still be waiting");
118
119 wait_task.await.expect("legacy shutdown wait task should complete");
120 }
121}