winbrew_core/
cancel.rs

1//! Cancellation support for long-running CLI and workflow operations.
2//!
3//! The cancel helper installs a Ctrl+C handler once per process and exposes a
4//! lightweight check that higher layers can call before expensive work.
5
6use anyhow::{Context, Result};
7use std::process;
8use std::sync::atomic::{AtomicBool, Ordering};
9use thiserror::Error;
10
11static HANDLER_INSTALLED: AtomicBool = AtomicBool::new(false);
12static CANCELLED: AtomicBool = AtomicBool::new(false);
13
14/// Error returned when an operation is interrupted by cancellation.
15#[derive(Debug, Error)]
16#[error("cancelled")]
17pub struct CancellationError;
18
19/// Install the process-wide Ctrl+C handler once.
20pub fn init_handler() -> Result<()> {
21    if HANDLER_INSTALLED
22        .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
23        .is_err()
24    {
25        return Ok(());
26    }
27
28    if let Err(err) = ctrlc::set_handler(|| {
29        if CANCELLED.swap(true, Ordering::Relaxed) {
30            process::exit(130);
31        }
32    }) {
33        HANDLER_INSTALLED.store(false, Ordering::Relaxed);
34        return Err(err).context("failed to install Ctrl+C handler");
35    }
36
37    Ok(())
38}
39
40/// Return `true` when the process has observed cancellation.
41pub fn is_cancelled() -> bool {
42    CANCELLED.load(Ordering::Relaxed)
43}
44
45/// Return an error when the process has been cancelled.
46pub fn check() -> std::result::Result<(), CancellationError> {
47    if is_cancelled() {
48        Err(CancellationError)
49    } else {
50        Ok(())
51    }
52}