1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// APCore Protocol — Cancellation tokens
// Spec reference: Cooperative cancellation for module execution
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
/// Error raised when an execution is cancelled mid-flight.
///
/// Mirrors `apcore-python.ExecutionCancelledError(Exception)` and carries the
/// same two fields: `module_id` (the module that was running) and `message`
/// (a human-readable cancellation reason).
#[derive(Debug, thiserror::Error)]
#[error("ExecutionCancelledError: module '{module_id}' — {message}")]
pub struct ExecutionCancelledError {
/// ID of the module whose execution was cancelled.
pub module_id: String,
/// Human-readable reason or description for the cancellation.
pub message: String,
}
/// Token used to signal cancellation to a running execution.
#[derive(Debug, Clone)]
pub struct CancelToken {
cancelled: Arc<AtomicBool>,
}
impl CancelToken {
/// Create a new cancel token in the non-cancelled state.
pub fn new() -> Self {
Self {
cancelled: Arc::new(AtomicBool::new(false)),
}
}
/// Signal cancellation.
pub fn cancel(&self) {
self.cancelled.store(true, Ordering::SeqCst);
}
/// Check whether cancellation has been requested.
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(Ordering::SeqCst)
}
/// Check if cancelled and return error if so.
pub fn check(&self) -> Result<(), crate::errors::ModuleError> {
if self.is_cancelled() {
Err(crate::errors::ModuleError::new(
crate::errors::ErrorCode::ExecutionCancelled,
"Execution was cancelled",
))
} else {
Ok(())
}
}
/// Reset the cancellation flag.
pub fn reset(&self) {
self.cancelled.store(false, Ordering::SeqCst);
}
}
impl Default for CancelToken {
fn default() -> Self {
Self::new()
}
}