Skip to main content

rusty_alto/
control.rs

1//! Cooperative cancellation for long-running parser operations.
2//!
3//! Ordinary callers can continue to use [`crate::Irtg::parse`] and
4//! [`crate::Irtg::parse_with`] without constructing a control. Applications
5//! that own cancellable jobs can clone a [`ParseControl`], pass one clone to
6//! [`crate::Irtg::parse_with_control`], and call [`ParseControl::cancel`] from
7//! another thread.
8//!
9//! Cancellation is cooperative: parsing returns
10//! [`crate::IrtgError::Cancelled`] after reaching a safe check point. It does
11//! not forcibly terminate a thread, and partial charts are not returned.
12
13use std::sync::{
14    Arc,
15    atomic::{AtomicBool, Ordering},
16};
17
18/// A clonable cancellation handle for a parse operation.
19///
20/// Existing parsing entry points do not require a control. Callers that need
21/// cancellation can create one, pass a reference to the controlled entry
22/// point, and call [`ParseControl::cancel`] from another thread.
23///
24/// Clones share cancellation state. A canceled control remains canceled, so
25/// create a fresh control for each independent parse job.
26#[derive(Clone, Debug, Default)]
27pub struct ParseControl {
28    cancelled: Arc<AtomicBool>,
29}
30
31impl ParseControl {
32    /// Create a control in the running state.
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    /// Request cooperative cancellation.
38    ///
39    /// This operation is thread-safe, inexpensive, and idempotent.
40    pub fn cancel(&self) {
41        self.cancelled.store(true, Ordering::Relaxed);
42    }
43
44    /// Return whether cancellation has been requested.
45    pub fn is_cancelled(&self) -> bool {
46        self.cancelled.load(Ordering::Relaxed)
47    }
48
49    pub(crate) fn check(&self) -> Result<(), ParseCancelled> {
50        if self.is_cancelled() {
51            Err(ParseCancelled)
52        } else {
53            Ok(())
54        }
55    }
56}
57
58#[derive(Clone, Copy, Debug)]
59pub(crate) struct ParseCancelled;
60
61#[cfg(test)]
62mod tests {
63    use super::ParseControl;
64
65    #[test]
66    fn cloned_control_observes_cancellation() {
67        let control = ParseControl::new();
68        let worker_control = control.clone();
69
70        control.cancel();
71
72        assert!(worker_control.is_cancelled());
73        assert!(worker_control.check().is_err());
74    }
75}