Skip to main content

asupersync/types/
task_context.rs

1//! Internal state shared between TaskRecord and Cx.
2
3use crate::types::{Budget, CancelReason, RegionId, TaskId, Time};
4use std::task::Waker;
5
6/// Maximum nesting depth for `Cx::masked()` sections.
7///
8/// Enforces the INV-MASK-BOUNDED invariant from the formal semantics:
9/// a task's mask depth must be finite and bounded to guarantee that
10/// cancellation cannot be deferred indefinitely. Exceeding this limit
11/// indicates a programming error (excessive nesting of masked critical
12/// sections).
13pub const MAX_MASK_DEPTH: u32 = 64;
14
15/// State for tracking checkpoint progress.
16///
17/// This struct tracks progress reporting checkpoints, which are distinct from
18/// cancellation checkpoints. Progress checkpoints indicate that a task is
19/// making forward progress and are useful for:
20/// - Detecting stuck/stalled tasks
21/// - Work-stealing scheduler decisions
22/// - Observability and debugging
23#[derive(Debug, Clone)]
24pub struct CheckpointState {
25    /// The runtime time of the last checkpoint.
26    pub last_checkpoint: Option<Time>,
27    /// The message from the last `checkpoint_with()` call.
28    pub last_message: Option<String>,
29    /// The total number of checkpoints recorded.
30    pub checkpoint_count: u64,
31}
32
33impl Default for CheckpointState {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl CheckpointState {
40    /// Creates a new checkpoint state with no recorded checkpoints.
41    #[inline]
42    #[must_use]
43    pub fn new() -> Self {
44        Self {
45            last_checkpoint: None,
46            last_message: None,
47            checkpoint_count: 0,
48        }
49    }
50
51    /// Records a checkpoint without a message.
52    #[inline]
53    pub fn record(&mut self) {
54        self.record_at(crate::time::wall_now());
55    }
56
57    /// Records a checkpoint at an explicit runtime time.
58    #[inline]
59    pub fn record_at(&mut self, at: Time) {
60        self.last_checkpoint = Some(at);
61        self.last_message = None;
62        self.checkpoint_count += 1;
63    }
64
65    /// Records a checkpoint with a message.
66    #[inline]
67    pub fn record_with_message(&mut self, message: String) {
68        self.record_with_message_at(message, crate::time::wall_now());
69    }
70
71    /// Records a checkpoint with a message at an explicit runtime time.
72    #[inline]
73    pub fn record_with_message_at(&mut self, message: String, at: Time) {
74        self.last_checkpoint = Some(at);
75        self.last_message = Some(message);
76        self.checkpoint_count += 1;
77    }
78}
79
80/// Internal state for a capability context.
81///
82/// This struct is shared between the user-facing `Cx` and the runtime's
83/// `TaskRecord`, ensuring that cancellation signals and budget updates
84/// are synchronized.
85#[derive(Debug)]
86pub struct CxInner {
87    /// The region this context belongs to.
88    pub region: RegionId,
89    /// The task this context belongs to.
90    pub task: TaskId,
91    /// Optional task type label for adaptive monitoring/metrics.
92    pub task_type: Option<String>,
93    /// Current budget.
94    pub budget: Budget,
95    /// Baseline budget used for checkpoint accounting.
96    pub budget_baseline: Budget,
97    /// Whether cancellation has been requested.
98    pub cancel_requested: bool,
99    /// The reason for cancellation, if requested.
100    pub cancel_reason: Option<CancelReason>,
101    /// Whether cancellation has been acknowledged at a checkpoint.
102    pub cancel_acknowledged: bool,
103    /// Waker used to schedule cancellation promptly.
104    pub cancel_waker: Option<Waker>,
105    /// Current mask depth.
106    pub mask_depth: u32,
107    /// Progress checkpoint state.
108    pub checkpoint_state: CheckpointState,
109    /// Fast atomic flag for cancellation (avoids RwLock on wake hot path).
110    pub fast_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
111}
112
113impl CxInner {
114    /// Creates a new CxInner.
115    #[must_use]
116    pub fn new(region: RegionId, task: TaskId, budget: Budget) -> Self {
117        Self {
118            region,
119            task,
120            task_type: None,
121            budget,
122            budget_baseline: budget,
123            cancel_requested: false,
124            cancel_reason: None,
125            cancel_acknowledged: false,
126            cancel_waker: None,
127            mask_depth: 0,
128            checkpoint_state: CheckpointState::new(),
129            fast_cancel: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
130        }
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    fn init_test(name: &str) {
139        crate::test_utils::init_test_logging();
140        crate::test_phase!(name);
141    }
142
143    #[test]
144    fn test_checkpoint_state_default() {
145        init_test("test_checkpoint_state_default");
146        let state = CheckpointState::new();
147        crate::assert_with_log!(
148            state.last_checkpoint.is_none(),
149            "last_checkpoint",
150            true,
151            state.last_checkpoint.is_none()
152        );
153        crate::assert_with_log!(
154            state.last_message.is_none(),
155            "last_message",
156            true,
157            state.last_message.is_none()
158        );
159        crate::assert_with_log!(
160            state.checkpoint_count == 0,
161            "checkpoint_count",
162            0,
163            state.checkpoint_count
164        );
165        crate::test_complete!("test_checkpoint_state_default");
166    }
167
168    #[test]
169    fn test_checkpoint_state_record() {
170        init_test("test_checkpoint_state_record");
171        let mut state = CheckpointState::new();
172        state.record();
173        crate::assert_with_log!(
174            state.last_checkpoint.is_some(),
175            "last_checkpoint",
176            true,
177            state.last_checkpoint.is_some()
178        );
179        crate::assert_with_log!(
180            state.last_message.is_none(),
181            "last_message",
182            true,
183            state.last_message.is_none()
184        );
185        crate::assert_with_log!(
186            state.checkpoint_count == 1,
187            "checkpoint_count",
188            1,
189            state.checkpoint_count
190        );
191        state.record();
192        crate::assert_with_log!(
193            state.checkpoint_count == 2,
194            "checkpoint_count 2",
195            2,
196            state.checkpoint_count
197        );
198        crate::test_complete!("test_checkpoint_state_record");
199    }
200
201    #[test]
202    fn test_checkpoint_state_record_at() {
203        init_test("test_checkpoint_state_record_at");
204        let mut state = CheckpointState::new();
205        let at = Time::from_nanos(123);
206
207        state.record_at(at);
208
209        crate::assert_with_log!(
210            state.last_checkpoint == Some(at),
211            "explicit checkpoint instant stored",
212            format!("{at:?}"),
213            format!("{:?}", state.last_checkpoint)
214        );
215        crate::assert_with_log!(
216            state.last_message.is_none(),
217            "record_at clears message",
218            true,
219            state.last_message.is_none()
220        );
221        crate::assert_with_log!(
222            state.checkpoint_count == 1,
223            "record_at increments count",
224            1,
225            state.checkpoint_count
226        );
227        crate::test_complete!("test_checkpoint_state_record_at");
228    }
229
230    #[test]
231    fn test_checkpoint_state_record_with_message() {
232        init_test("test_checkpoint_state_record_with_message");
233        let mut state = CheckpointState::new();
234        state.record_with_message("hello".to_string());
235        crate::assert_with_log!(
236            state.last_checkpoint.is_some(),
237            "last_checkpoint",
238            true,
239            state.last_checkpoint.is_some()
240        );
241        crate::assert_with_log!(
242            state.last_message.as_deref() == Some("hello"),
243            "last_message",
244            Some("hello"),
245            state.last_message.as_deref()
246        );
247        crate::assert_with_log!(
248            state.checkpoint_count == 1,
249            "checkpoint_count",
250            1,
251            state.checkpoint_count
252        );
253        state.record();
254        crate::assert_with_log!(
255            state.last_message.is_none(),
256            "last_message cleared",
257            true,
258            state.last_message.is_none()
259        );
260        crate::test_complete!("test_checkpoint_state_record_with_message");
261    }
262
263    #[test]
264    fn test_checkpoint_state_record_with_message_at() {
265        init_test("test_checkpoint_state_record_with_message_at");
266        let mut state = CheckpointState::new();
267        let at = Time::from_nanos(456);
268
269        state.record_with_message_at("hello".to_string(), at);
270
271        crate::assert_with_log!(
272            state.last_checkpoint == Some(at),
273            "explicit checkpoint instant stored",
274            format!("{at:?}"),
275            format!("{:?}", state.last_checkpoint)
276        );
277        crate::assert_with_log!(
278            state.last_message.as_deref() == Some("hello"),
279            "record_with_message_at stores message",
280            Some("hello"),
281            state.last_message.as_deref()
282        );
283        crate::assert_with_log!(
284            state.checkpoint_count == 1,
285            "record_with_message_at increments count",
286            1,
287            state.checkpoint_count
288        );
289        crate::test_complete!("test_checkpoint_state_record_with_message_at");
290    }
291
292    #[test]
293    fn test_checkpoint_state_message_overwrite() {
294        init_test("test_checkpoint_state_message_overwrite");
295        let mut state = CheckpointState::new();
296        state.record_with_message("first".to_string());
297        state.record_with_message("second".to_string());
298        crate::assert_with_log!(
299            state.last_message.as_deref() == Some("second"),
300            "last_message overwrite",
301            Some("second"),
302            state.last_message.as_deref()
303        );
304        crate::assert_with_log!(
305            state.checkpoint_count == 2,
306            "checkpoint_count",
307            2,
308            state.checkpoint_count
309        );
310        crate::test_complete!("test_checkpoint_state_message_overwrite");
311    }
312
313    #[test]
314    fn test_cx_inner_new() {
315        init_test("test_cx_inner_new");
316        let region = RegionId::testing_default();
317        let task = TaskId::testing_default();
318        let budget = Budget::new();
319        let cx = CxInner::new(region, task, budget);
320        crate::assert_with_log!(cx.region == region, "region", region, cx.region);
321        crate::assert_with_log!(cx.task == task, "task", task, cx.task);
322        crate::assert_with_log!(cx.budget == budget, "budget", budget, cx.budget);
323        crate::assert_with_log!(
324            cx.budget_baseline == budget,
325            "budget_baseline",
326            budget,
327            cx.budget_baseline
328        );
329        crate::assert_with_log!(
330            !cx.cancel_requested,
331            "cancel_requested",
332            false,
333            cx.cancel_requested
334        );
335        crate::assert_with_log!(
336            cx.cancel_reason.is_none(),
337            "cancel_reason",
338            true,
339            cx.cancel_reason.is_none()
340        );
341        crate::assert_with_log!(cx.mask_depth == 0, "mask_depth", 0, cx.mask_depth);
342        crate::test_complete!("test_cx_inner_new");
343    }
344
345    // =========================================================================
346    // Wave 47 – pure data-type trait coverage
347    // =========================================================================
348
349    #[test]
350    fn checkpoint_state_debug_clone_default() {
351        let def = CheckpointState::default();
352        assert!(def.last_checkpoint.is_none());
353        assert!(def.last_message.is_none());
354        assert_eq!(def.checkpoint_count, 0);
355        let dbg = format!("{def:?}");
356        assert!(dbg.contains("CheckpointState"), "{dbg}");
357
358        let mut state = CheckpointState::new();
359        state.record_with_message("progress".into());
360        let cloned = state.clone();
361        assert_eq!(cloned.checkpoint_count, 1);
362        assert_eq!(cloned.last_message.as_deref(), Some("progress"));
363    }
364
365    #[test]
366    fn cx_inner_debug() {
367        let region = RegionId::testing_default();
368        let task = TaskId::testing_default();
369        let cx = CxInner::new(region, task, Budget::new());
370        let dbg = format!("{cx:?}");
371        assert!(dbg.contains("CxInner"), "{dbg}");
372    }
373}