Skip to main content

rskit_cli/
signal.rs

1//! Ctrl+C / graceful shutdown over [`tokio_util::sync::CancellationToken`].
2//!
3//! rskit standardizes on `tokio_util`'s [`CancellationToken`] as its single
4//! cooperative-cancellation type: `rskit-worker` handlers and `rskit-process`
5//! already consume it directly, so the CLI layer uses the *same* type end-to-end
6//! rather than introducing a parallel wrapper. The token is re-exported here so a
7//! CLI can name it without depending on `tokio-util` directly, and [`on_ctrl_c`]
8//! installs the one-shot interrupt handler that drives it.
9
10pub use tokio_util::sync::CancellationToken;
11
12/// Install a first-Ctrl+C handler and return the cooperative cancellation token.
13///
14/// Must be called from within a Tokio runtime: a background task awaits the
15/// interrupt signal and cancels the returned token on the first Ctrl+C. Clone the
16/// token and hand it to spawned tasks, an `rskit-worker` handler, or an
17/// `rskit-process` call; holders observe `is_cancelled()` / `cancelled()` and
18/// wind down gracefully.
19#[must_use]
20pub fn on_ctrl_c() -> CancellationToken {
21    let token = CancellationToken::new();
22    let child = token.clone();
23    tokio::spawn(async move {
24        if tokio::signal::ctrl_c().await.is_ok() {
25            child.cancel();
26        }
27    });
28    token
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34
35    #[tokio::test]
36    async fn cancellation_propagates_to_clones() {
37        let token = CancellationToken::new();
38        let clone = token.clone();
39
40        assert!(!token.is_cancelled());
41        assert!(!clone.is_cancelled());
42
43        token.cancel();
44
45        assert!(token.is_cancelled());
46        assert!(clone.is_cancelled());
47    }
48
49    #[tokio::test]
50    async fn on_ctrl_c_returns_a_live_uncancelled_token() {
51        let token = on_ctrl_c();
52        assert!(!token.is_cancelled());
53        // Cancelling locally still works; the interrupt task is just one source.
54        token.cancel();
55        token.cancelled().await;
56        assert!(token.is_cancelled());
57    }
58}