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}