knave 0.11.9

A Lisp programming language for humans and agents that infers types and ownership and compiles to readable Rust
Documentation
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::{Duration, Instant};

#[derive(Clone, Default)]
pub struct Interruption {
    raised: Option<Arc<AtomicBool>>,
}

impl Interruption {
    const HANDLER_LATENCY: Duration = Duration::from_millis(200);
    const POLL_INTERVAL: Duration = Duration::from_millis(5);

    pub fn catch() -> Result<Self, ctrlc::Error> {
        let raised = Arc::new(AtomicBool::new(false));
        let handler_flag = Arc::clone(&raised);
        ctrlc::set_handler(move || handler_flag.store(true, Ordering::SeqCst))?;
        Ok(Self::raised_by(raised))
    }

    fn raised_by(raised: Arc<AtomicBool>) -> Self {
        Self {
            raised: Some(raised),
        }
    }

    pub fn is_raised(&self) -> bool {
        self.raised
            .as_ref()
            .is_some_and(|raised| raised.load(Ordering::SeqCst))
    }

    pub fn caused_a_failure(&self) -> bool {
        self.raised
            .as_ref()
            .is_some_and(Self::raised_within_latency)
    }

    pub fn acknowledge(&self) {
        self.wait_for_the_handler();
        self.clear();
    }

    fn wait_for_the_handler(&self) {
        if let Some(raised) = &self.raised {
            Self::raised_within_latency(raised);
        }
    }

    pub fn clear(&self) {
        if let Some(raised) = &self.raised {
            raised.store(false, Ordering::SeqCst);
        }
    }

    fn raised_within_latency(raised: &Arc<AtomicBool>) -> bool {
        let deadline = Instant::now() + Self::HANDLER_LATENCY;
        while !raised.load(Ordering::SeqCst) {
            if Instant::now() >= deadline {
                return false;
            }
            thread::sleep(Self::POLL_INTERVAL);
        }
        true
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    struct LateHandler {
        flag: Arc<AtomicBool>,
    }

    impl LateHandler {
        const DELAY: Duration = Duration::from_millis(50);

        fn new() -> Self {
            Self {
                flag: Arc::new(AtomicBool::new(false)),
            }
        }

        fn interruption(&self) -> Interruption {
            Interruption::raised_by(Arc::clone(&self.flag))
        }

        fn raise_after_delay(&self) -> thread::JoinHandle<()> {
            let flag = Arc::clone(&self.flag);
            thread::spawn(move || {
                thread::sleep(Self::DELAY);
                flag.store(true, Ordering::SeqCst);
            })
        }
    }

    #[test]
    fn an_acknowledged_interruption_stays_cleared_when_the_handler_runs_late() {
        let handler = LateHandler::new();
        let interruption = handler.interruption();
        let raising = handler.raise_after_delay();
        interruption.acknowledge();
        raising.join().unwrap();
        assert!(!interruption.is_raised());
    }
}