1use std::future::Future;
30use std::marker::PhantomData;
31use std::pin::Pin;
32#[cfg(feature = "native")]
33use std::sync::atomic::AtomicU8;
34use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
35use std::sync::{Arc, Mutex, Weak};
36use std::task::{Context as TaskContext, Poll, Waker};
37use std::time::Duration;
38
39use crate::sync_primitives::SystemTime;
40
41#[cfg(feature = "native")]
42use asupersync::types::Time as NativeTime;
43#[cfg(feature = "native")]
44use asupersync::types::{CancelKind as NativeCancelKind, CancelReason as NativeCancelReason};
45#[cfg(feature = "native")]
46use asupersync::{Budget as NativeBudget, Cx as NativeCx};
47
48#[cfg(not(feature = "native"))]
49mod native_cx_shim {
50 use std::sync::atomic::{AtomicBool, Ordering};
51 use std::sync::{Arc, Mutex};
52
53 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
54 pub enum NativeCancelKind {
55 User,
56 Timeout,
57 Deadline,
58 PollQuota,
59 CostBudget,
60 FailFast,
61 RaceLost,
62 ParentCancelled,
63 Shutdown,
64 LinkedExit,
65 ResourceUnavailable,
66 }
67
68 #[derive(Debug, Clone, PartialEq, Eq)]
69 pub struct NativeCancelReason {
70 pub kind: NativeCancelKind,
71 }
72
73 impl NativeCancelReason {
74 #[must_use]
75 pub const fn timeout() -> Self {
76 Self {
77 kind: NativeCancelKind::Timeout,
78 }
79 }
80
81 #[must_use]
82 pub fn user(_message: impl Into<String>) -> Self {
83 Self {
84 kind: NativeCancelKind::User,
85 }
86 }
87
88 #[must_use]
89 pub const fn parent_cancelled() -> Self {
90 Self {
91 kind: NativeCancelKind::ParentCancelled,
92 }
93 }
94
95 #[must_use]
96 pub const fn resource_unavailable() -> Self {
97 Self {
98 kind: NativeCancelKind::ResourceUnavailable,
99 }
100 }
101 }
102
103 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
104 pub struct NativeCheckpointError;
105
106 #[derive(Debug, Default)]
107 struct NativeCxInner {
108 cancel_requested: AtomicBool,
109 cancel_reason: Mutex<Option<NativeCancelReason>>,
110 }
111
112 #[derive(Debug, Clone, Default)]
113 pub struct NativeCx {
114 inner: Arc<NativeCxInner>,
115 }
116
117 impl NativeCx {
118 #[must_use]
119 pub fn for_testing() -> Self {
120 Self::default()
121 }
122
123 pub fn set_cancel_requested(&self, requested: bool) {
124 self.inner
125 .cancel_requested
126 .store(requested, Ordering::Release);
127 if !requested {
128 *self
129 .inner
130 .cancel_reason
131 .lock()
132 .unwrap_or_else(std::sync::PoisonError::into_inner) = None;
133 }
134 }
135
136 pub fn set_cancel_reason(&self, reason: NativeCancelReason) {
137 *self
138 .inner
139 .cancel_reason
140 .lock()
141 .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(reason);
142 self.inner.cancel_requested.store(true, Ordering::Release);
143 }
144
145 #[must_use]
146 pub fn is_cancel_requested(&self) -> bool {
147 self.inner.cancel_requested.load(Ordering::Acquire)
148 }
149
150 #[must_use]
151 pub fn cancel_reason(&self) -> Option<NativeCancelReason> {
152 self.inner
153 .cancel_reason
154 .lock()
155 .unwrap_or_else(std::sync::PoisonError::into_inner)
156 .clone()
157 }
158
159 pub fn checkpoint(&self) -> std::result::Result<(), NativeCheckpointError> {
160 if self.is_cancel_requested() {
161 Err(NativeCheckpointError)
162 } else {
163 Ok(())
164 }
165 }
166 }
167}
168
169#[cfg(not(feature = "native"))]
170use native_cx_shim::NativeCx;
171
172use crate::eprocess::{EProcessDecision, EProcessOracle, EProcessSnapshot};
173
174pub const SQLITE_INTERRUPT: i32 = 9;
176
177pub const MAX_MASK_DEPTH: u32 = 64;
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
192pub enum CancelState {
193 Created,
194 Running,
195 CancelRequested,
196 Cancelling,
197 Finalizing,
198 Completed,
199}
200
201#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
206pub enum CancelReason {
207 Timeout = 0,
208 UserInterrupt = 1,
209 RegionClose = 2,
210 Abort = 3,
211}
212
213pub mod cap {
215 mod sealed {
216 pub trait Sealed {}
217
218 pub struct Bit<const V: bool>;
219
220 pub trait Le {}
221 impl Le for (Bit<false>, Bit<false>) {}
222 impl Le for (Bit<false>, Bit<true>) {}
223 impl Le for (Bit<true>, Bit<true>) {}
224 }
225
226 #[derive(Debug, Clone, Copy, Default)]
228 pub struct CapSet<
229 const SPAWN: bool,
230 const TIME: bool,
231 const RANDOM: bool,
232 const IO: bool,
233 const REMOTE: bool,
234 >;
235
236 impl<
237 const SPAWN: bool,
238 const TIME: bool,
239 const RANDOM: bool,
240 const IO: bool,
241 const REMOTE: bool,
242 > sealed::Sealed for CapSet<SPAWN, TIME, RANDOM, IO, REMOTE>
243 {
244 }
245
246 pub type All = CapSet<true, true, true, true, true>;
248 pub type None = CapSet<false, false, false, false, false>;
250
251 pub trait SubsetOf<Super>: sealed::Sealed {}
256
257 impl<
258 const S_SPAWN: bool,
259 const S_TIME: bool,
260 const S_RANDOM: bool,
261 const S_IO: bool,
262 const S_REMOTE: bool,
263 const P_SPAWN: bool,
264 const P_TIME: bool,
265 const P_RANDOM: bool,
266 const P_IO: bool,
267 const P_REMOTE: bool,
268 > SubsetOf<CapSet<P_SPAWN, P_TIME, P_RANDOM, P_IO, P_REMOTE>>
269 for CapSet<S_SPAWN, S_TIME, S_RANDOM, S_IO, S_REMOTE>
270 where
271 (sealed::Bit<S_SPAWN>, sealed::Bit<P_SPAWN>): sealed::Le,
272 (sealed::Bit<S_TIME>, sealed::Bit<P_TIME>): sealed::Le,
273 (sealed::Bit<S_RANDOM>, sealed::Bit<P_RANDOM>): sealed::Le,
274 (sealed::Bit<S_IO>, sealed::Bit<P_IO>): sealed::Le,
275 (sealed::Bit<S_REMOTE>, sealed::Bit<P_REMOTE>): sealed::Le,
276 {
277 }
278
279 pub trait HasSpawn: sealed::Sealed {}
280 impl<const TIME: bool, const RANDOM: bool, const IO: bool, const REMOTE: bool> HasSpawn
281 for CapSet<true, TIME, RANDOM, IO, REMOTE>
282 {
283 }
284
285 pub trait HasTime: sealed::Sealed {}
286 impl<const SPAWN: bool, const RANDOM: bool, const IO: bool, const REMOTE: bool> HasTime
287 for CapSet<SPAWN, true, RANDOM, IO, REMOTE>
288 {
289 }
290
291 pub trait HasRandom: sealed::Sealed {}
292 impl<const SPAWN: bool, const TIME: bool, const IO: bool, const REMOTE: bool> HasRandom
293 for CapSet<SPAWN, TIME, true, IO, REMOTE>
294 {
295 }
296
297 pub trait HasIo: sealed::Sealed {}
298 impl<const SPAWN: bool, const TIME: bool, const RANDOM: bool, const REMOTE: bool> HasIo
299 for CapSet<SPAWN, TIME, RANDOM, true, REMOTE>
300 {
301 }
302
303 pub trait HasRemote: sealed::Sealed {}
304 impl<const SPAWN: bool, const TIME: bool, const RANDOM: bool, const IO: bool> HasRemote
305 for CapSet<SPAWN, TIME, RANDOM, IO, true>
306 {
307 }
308}
309
310pub type FullCaps = cap::All;
312pub type StorageCaps = cap::CapSet<false, true, false, true, false>;
314pub type ComputeCaps = cap::None;
316
317#[derive(Debug, Clone, Copy, PartialEq, Eq)]
323pub struct Budget {
324 pub deadline: Option<Duration>,
327 pub poll_quota: u32,
328 pub cost_quota: Option<u64>,
329 pub priority: u8,
330}
331
332impl Budget {
333 pub const INFINITE: Self = Self {
335 deadline: None,
336 poll_quota: u32::MAX,
337 cost_quota: None,
338 priority: 0,
339 };
340
341 pub const MINIMAL: Self = Self {
343 deadline: None,
344 poll_quota: 100,
345 cost_quota: None,
346 priority: 0,
347 };
348
349 #[must_use]
350 pub const fn with_deadline(self, deadline: Duration) -> Self {
351 Self {
352 deadline: Some(deadline),
353 ..self
354 }
355 }
356
357 #[must_use]
358 pub const fn with_priority(self, priority: u8) -> Self {
359 Self { priority, ..self }
360 }
361
362 #[must_use]
363 pub const fn with_poll_quota(self, poll_quota: u32) -> Self {
364 Self { poll_quota, ..self }
365 }
366
367 #[must_use]
368 pub const fn with_cost_quota(self, cost_quota: u64) -> Self {
369 Self {
370 cost_quota: Some(cost_quota),
371 ..self
372 }
373 }
374
375 #[must_use]
377 pub fn meet(self, other: Self) -> Self {
378 Self {
379 deadline: match (self.deadline, other.deadline) {
380 (Some(a), Some(b)) => Some(a.min(b)),
381 (Some(a), None) => Some(a),
382 (None, Some(b)) => Some(b),
383 (None, None) => None,
384 },
385 poll_quota: self.poll_quota.min(other.poll_quota),
386 cost_quota: match (self.cost_quota, other.cost_quota) {
387 (Some(a), Some(b)) => Some(a.min(b)),
388 (Some(a), None) => Some(a),
389 (None, Some(b)) => Some(b),
390 (None, None) => None,
391 },
392 priority: self.priority.max(other.priority),
393 }
394 }
395}
396
397#[derive(Debug, Clone, Copy, PartialEq, Eq)]
398pub enum ErrorKind {
399 Cancelled,
400}
401
402#[derive(Debug, Clone, PartialEq, Eq)]
403pub struct Error {
404 kind: ErrorKind,
405}
406
407impl std::fmt::Display for Error {
408 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409 match self.kind {
410 ErrorKind::Cancelled => write!(f, "operation cancelled"),
411 }
412 }
413}
414
415impl std::error::Error for Error {}
416
417impl Error {
418 #[must_use]
419 pub const fn cancelled() -> Self {
420 Self {
421 kind: ErrorKind::Cancelled,
422 }
423 }
424
425 #[must_use]
426 pub const fn kind(&self) -> ErrorKind {
427 self.kind
428 }
429
430 #[must_use]
431 pub const fn sqlite_error_code(&self) -> i32 {
432 match self.kind {
433 ErrorKind::Cancelled => SQLITE_INTERRUPT,
434 }
435 }
436}
437
438pub type Result<T, E = Error> = std::result::Result<T, E>;
439
440#[derive(Debug)]
441struct LocalCancelWaiter {
442 id: u64,
443 waker: Waker,
444}
445
446#[derive(Debug, Default)]
447struct LocalCancelWaiters {
448 next_id: u64,
449 entries: Vec<LocalCancelWaiter>,
450}
451
452impl LocalCancelWaiters {
453 fn allocate_id(&mut self) -> u64 {
454 loop {
455 let id = self.next_id;
456 self.next_id = self.next_id.wrapping_add(1);
457 if self.entries.iter().all(|waiter| waiter.id != id) {
458 return id;
459 }
460 }
461 }
462
463 fn remove(&mut self, id: u64) -> Option<LocalCancelWaiter> {
464 self.entries
465 .iter()
466 .position(|waiter| waiter.id == id)
467 .map(|position| self.entries.swap_remove(position))
468 }
469}
470
471#[derive(Debug)]
472struct CxInner {
473 cancel_requested: AtomicBool,
474 #[cfg(feature = "native")]
479 native_cancel_reason: AtomicU8,
480 cancel_state: Mutex<CancelState>,
481 cancel_reason: Mutex<Option<CancelReason>>,
482 mask_depth: AtomicU32,
483 cancel_dispatch_gate: Arc<Mutex<()>>,
484 local_cancel_waiters: Mutex<LocalCancelWaiters>,
485 children: Mutex<Vec<Weak<Self>>>,
486 last_checkpoint_msg: Mutex<Option<String>>,
487 last_eprocess_decision: Mutex<Option<EProcessDecision>>,
488 eprocess_oracle: std::sync::OnceLock<Arc<EProcessOracle>>,
489 #[cfg(feature = "native")]
490 attached_native_cx: Mutex<Option<NativeCx>>,
491 #[cfg(feature = "native")]
492 fallback_native_cx: std::sync::OnceLock<NativeCx>,
493 blocking_io_inline_safe: AtomicBool,
500 unix_millis: AtomicU64,
507 unix_millis_is_fixed: AtomicBool,
508}
509
510impl CxInner {
511 fn new(cancel_dispatch_gate: Arc<Mutex<()>>) -> Self {
512 Self {
513 cancel_requested: AtomicBool::new(false),
514 #[cfg(feature = "native")]
515 native_cancel_reason: AtomicU8::new(0),
516 cancel_state: Mutex::new(CancelState::Created),
517 cancel_reason: Mutex::new(None),
518 mask_depth: AtomicU32::new(0),
519 cancel_dispatch_gate,
520 local_cancel_waiters: Mutex::new(LocalCancelWaiters::default()),
521 children: Mutex::new(Vec::new()),
522 last_checkpoint_msg: Mutex::new(None),
523 last_eprocess_decision: Mutex::new(None),
524 eprocess_oracle: std::sync::OnceLock::new(),
525 #[cfg(feature = "native")]
526 attached_native_cx: Mutex::new(None),
527 #[cfg(feature = "native")]
528 fallback_native_cx: std::sync::OnceLock::new(),
529 blocking_io_inline_safe: AtomicBool::new(false),
530 unix_millis: AtomicU64::new(0),
531 unix_millis_is_fixed: AtomicBool::new(false),
532 }
533 }
534}
535
536#[cfg(feature = "native")]
537#[must_use]
538fn local_reason_to_native(reason: CancelReason) -> NativeCancelReason {
539 match reason {
540 CancelReason::Timeout => NativeCancelReason::timeout(),
541 CancelReason::UserInterrupt => NativeCancelReason::user("sqlite interrupt"),
542 CancelReason::RegionClose => NativeCancelReason::parent_cancelled(),
543 CancelReason::Abort => NativeCancelReason::resource_unavailable(),
544 }
545}
546
547#[cfg(feature = "native")]
548#[must_use]
549fn native_reason_to_local(reason: &NativeCancelReason) -> CancelReason {
550 match reason.kind {
551 NativeCancelKind::User => CancelReason::UserInterrupt,
552 NativeCancelKind::Timeout
553 | NativeCancelKind::Deadline
554 | NativeCancelKind::PollQuota
555 | NativeCancelKind::CostBudget => CancelReason::Timeout,
556 NativeCancelKind::FailFast
557 | NativeCancelKind::RaceLost
558 | NativeCancelKind::ParentCancelled
559 | NativeCancelKind::Shutdown
560 | NativeCancelKind::LinkedExit => CancelReason::RegionClose,
561 NativeCancelKind::ResourceUnavailable => CancelReason::Abort,
562 }
563}
564
565#[cfg(feature = "native")]
566const fn encode_native_cancel_reason(reason: CancelReason) -> u8 {
567 match reason {
568 CancelReason::Timeout => 1,
569 CancelReason::UserInterrupt => 2,
570 CancelReason::RegionClose => 3,
571 CancelReason::Abort => 4,
572 }
573}
574
575#[cfg(feature = "native")]
576fn decode_native_cancel_reason(encoded: u8) -> Option<CancelReason> {
577 match encoded {
578 0 => None,
579 1 => Some(CancelReason::Timeout),
580 2 => Some(CancelReason::UserInterrupt),
581 3 => Some(CancelReason::RegionClose),
582 4 => Some(CancelReason::Abort),
583 _ => unreachable!("invalid native cancellation reason rank"),
584 }
585}
586
587#[cfg(feature = "native")]
588fn record_native_cancel_reason(inner: &CxInner, reason: CancelReason) {
589 inner
590 .native_cancel_reason
591 .fetch_max(encode_native_cancel_reason(reason), Ordering::AcqRel);
592}
593
594#[cfg(feature = "native")]
595fn mirrored_native_cancel_reason(inner: &CxInner) -> Option<CancelReason> {
596 decode_native_cancel_reason(inner.native_cancel_reason.load(Ordering::Acquire))
597}
598
599#[cfg(feature = "native")]
600fn sync_one_native_cx_cancel(inner: &CxInner, native: &NativeCx) {
601 let mut encoded = inner.native_cancel_reason.load(Ordering::Acquire);
602 while let Some(reason) = decode_native_cancel_reason(encoded) {
603 native.set_cancel_reason(local_reason_to_native(reason));
606 let latest = inner.native_cancel_reason.load(Ordering::Acquire);
607 if latest == encoded {
608 break;
609 }
610 encoded = latest;
611 }
612}
613
614#[cfg(feature = "native")]
615#[must_use]
616#[allow(dead_code)]
617fn native_budget_from_local_at(budget: Budget, now: NativeTime) -> NativeBudget {
618 let mut native_budget = NativeBudget::new()
619 .with_poll_quota(budget.poll_quota)
620 .with_priority(budget.priority);
621 if let Some(cost_quota) = budget.cost_quota {
622 native_budget = native_budget.with_cost_quota(cost_quota);
623 }
624 if let Some(timeout) = budget.deadline {
625 native_budget = native_budget.with_timeout(now, timeout);
626 }
627 native_budget
628}
629
630#[derive(Debug, Clone, Copy, PartialEq, Eq)]
631enum NativeCancelPropagation {
632 LocalAndNative,
633 LocalOnly,
634}
635
636#[must_use]
637fn local_cancellation_matches(inner: &CxInner, respect_mask: bool) -> bool {
638 inner.cancel_requested.load(Ordering::Acquire)
639 && (!respect_mask || inner.mask_depth.load(Ordering::Acquire) == 0)
640}
641
642fn take_local_cancel_waiters(inner: &CxInner) -> Vec<LocalCancelWaiter> {
643 let mut waiters = inner
644 .local_cancel_waiters
645 .lock()
646 .unwrap_or_else(std::sync::PoisonError::into_inner);
647 std::mem::take(&mut waiters.entries)
648}
649
650fn capture_cancel_callback_panic(
651 first_panic: &mut Option<Box<dyn std::any::Any + Send>>,
652 callback: impl FnOnce(),
653) {
654 if let Err(payload) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(callback)) {
655 if first_panic.is_none() {
656 *first_panic = Some(payload);
657 } else {
658 std::mem::forget(payload);
662 }
663 }
664}
665
666fn dispatch_local_cancel_waiters(
667 waiters: Vec<LocalCancelWaiter>,
668 first_panic: &mut Option<Box<dyn std::any::Any + Send>>,
669) {
670 for waiter in waiters {
675 capture_cancel_callback_panic(first_panic, || waiter.waker.wake_by_ref());
676 capture_cancel_callback_panic(first_panic, || drop(waiter));
677 }
678}
679
680fn publish_cancel_state(
681 inner: &CxInner,
682 local_reason: CancelReason,
683 native_reason: Option<CancelReason>,
684) -> CancelReason {
685 #[cfg(not(feature = "native"))]
686 let _ = native_reason;
687
688 let effective_local_reason = {
690 let mut r = inner
691 .cancel_reason
692 .lock()
693 .unwrap_or_else(std::sync::PoisonError::into_inner);
694 match *r {
695 Some(existing) if existing >= local_reason => existing,
696 _ => {
697 *r = Some(local_reason);
698 local_reason
699 }
700 }
701 };
702
703 {
705 let mut state = inner
706 .cancel_state
707 .lock()
708 .unwrap_or_else(std::sync::PoisonError::into_inner);
709 if matches!(*state, CancelState::Created | CancelState::Running) {
710 *state = CancelState::CancelRequested;
711 }
712 }
713
714 #[cfg(feature = "native")]
719 if let Some(reason) = native_reason {
720 record_native_cancel_reason(inner, reason);
721 }
722
723 inner.cancel_requested.store(true, Ordering::Release);
727
728 effective_local_reason
729}
730
731fn try_append_live_children(
732 inner: &CxInner,
733 descendants: &mut Vec<Arc<CxInner>>,
734) -> std::result::Result<(), std::collections::TryReserveError> {
735 let mut children = inner
736 .children
737 .lock()
738 .unwrap_or_else(std::sync::PoisonError::into_inner);
739 children.retain(|child| child.strong_count() > 0);
740 descendants.try_reserve(children.len())?;
741 for child in children.iter().filter_map(Weak::upgrade) {
742 descendants.push(child);
743 }
744 Ok(())
745}
746
747#[must_use]
748fn local_cancel_waiter_count(inner: &CxInner) -> usize {
749 inner
750 .local_cancel_waiters
751 .lock()
752 .unwrap_or_else(std::sync::PoisonError::into_inner)
753 .entries
754 .len()
755}
756
757fn drain_local_cancel_waiters_into(inner: &CxInner, waiters: &mut Vec<LocalCancelWaiter>) {
758 let mut registered_waiters = inner
759 .local_cancel_waiters
760 .lock()
761 .unwrap_or_else(std::sync::PoisonError::into_inner);
762 debug_assert!(
763 waiters.capacity() - waiters.len() >= registered_waiters.entries.len(),
764 "cancellation waiter capacity must be reserved before publication"
765 );
766 waiters.append(&mut registered_waiters.entries);
767}
768
769#[cfg(feature = "native")]
770struct NativeCancelTarget {
771 descendant_index: Option<usize>,
772 native_cx: NativeCx,
773}
774
775#[cfg(feature = "native")]
776fn append_native_cancel_targets(
777 inner: &CxInner,
778 descendant_index: Option<usize>,
779 targets: &mut Vec<NativeCancelTarget>,
780) {
781 let attached_native = inner
782 .attached_native_cx
783 .lock()
784 .unwrap_or_else(std::sync::PoisonError::into_inner)
785 .as_ref()
786 .cloned();
787 if let Some(native_cx) = attached_native {
788 targets.push(NativeCancelTarget {
789 descendant_index,
790 native_cx,
791 });
792 }
793
794 if let Some(native_cx) = inner.fallback_native_cx.get().cloned() {
795 targets.push(NativeCancelTarget {
796 descendant_index,
797 native_cx,
798 });
799 }
800}
801
802fn propagate_cancel_tree(
807 inner: &CxInner,
808 reason: CancelReason,
809 native_propagation: NativeCancelPropagation,
810) {
811 let mut descendants = Vec::new();
812 let mut waiters = Vec::new();
813 #[cfg(feature = "native")]
814 let mut native_targets = Vec::new();
815 let mut reserve_error = None;
816 {
817 let _dispatch = inner
821 .cancel_dispatch_gate
822 .lock()
823 .unwrap_or_else(std::sync::PoisonError::into_inner);
824 let native_reason = match native_propagation {
825 NativeCancelPropagation::LocalAndNative => Some(reason),
826 NativeCancelPropagation::LocalOnly => None,
827 };
828
829 if let Err(error) = try_append_live_children(inner, &mut descendants) {
830 reserve_error = Some(error);
831 }
832 let mut cursor = 0;
833 while reserve_error.is_none() && cursor < descendants.len() {
834 let node = Arc::clone(&descendants[cursor]);
835 if let Err(error) = try_append_live_children(&node, &mut descendants) {
836 reserve_error = Some(error);
837 break;
838 }
839 cursor += 1;
840 }
841
842 if reserve_error.is_none() {
843 let waiter_count = descendants
847 .iter()
848 .fold(local_cancel_waiter_count(inner), |count, node| {
849 count.saturating_add(local_cancel_waiter_count(node))
850 });
851 if let Err(error) = waiters.try_reserve(waiter_count) {
852 reserve_error = Some(error);
853 }
854 }
855
856 #[cfg(feature = "native")]
857 if reserve_error.is_none() && native_propagation == NativeCancelPropagation::LocalAndNative
858 {
859 let target_capacity = descendants.len().saturating_mul(2).saturating_add(2);
860 if let Err(error) = native_targets.try_reserve(target_capacity) {
861 reserve_error = Some(error);
862 } else {
863 append_native_cancel_targets(inner, None, &mut native_targets);
864 for (index, node) in descendants.iter().enumerate() {
865 append_native_cancel_targets(node, Some(index), &mut native_targets);
866 }
867 }
868 }
869
870 if reserve_error.is_none() {
871 let propagated_local_reason = publish_cancel_state(inner, reason, native_reason);
872 drain_local_cancel_waiters_into(inner, &mut waiters);
873
874 #[cfg(feature = "native")]
875 let propagated_native_reason = match native_propagation {
876 NativeCancelPropagation::LocalAndNative => mirrored_native_cancel_reason(inner),
877 NativeCancelPropagation::LocalOnly => None,
878 };
879 #[cfg(not(feature = "native"))]
880 let propagated_native_reason = None;
881
882 for node in &descendants {
883 publish_cancel_state(node, propagated_local_reason, propagated_native_reason);
884 drain_local_cancel_waiters_into(node, &mut waiters);
885 }
886 }
887 }
888
889 if let Some(error) = reserve_error {
894 panic!("failed to reserve cancellation propagation storage: {error}");
895 }
896
897 let mut first_panic = None;
898 #[cfg(feature = "native")]
899 for target in &native_targets {
900 let target_inner = target
901 .descendant_index
902 .map_or(inner, |index| &descendants[index]);
903 capture_cancel_callback_panic(&mut first_panic, || {
904 sync_one_native_cx_cancel(target_inner, &target.native_cx);
905 });
906 }
907 dispatch_local_cancel_waiters(waiters, &mut first_panic);
908 if let Some(payload) = first_panic {
909 std::panic::resume_unwind(payload);
910 }
911}
912
913fn propagate_cancel(inner: &CxInner, reason: CancelReason) {
914 propagate_cancel_tree(inner, reason, NativeCancelPropagation::LocalAndNative);
915}
916
917fn propagate_local_cancel(inner: &CxInner, reason: CancelReason) {
918 propagate_cancel_tree(inner, reason, NativeCancelPropagation::LocalOnly);
919}
920
921#[derive(Debug)]
931#[must_use = "dropping the relay discards the authority to cancel the derived operation"]
932pub struct LocalCancelRelay {
933 inner: Weak<CxInner>,
934}
935
936impl LocalCancelRelay {
937 #[must_use]
943 pub fn cancel_local(&self, reason: CancelReason) -> bool {
944 let Some(inner) = self.inner.upgrade() else {
945 return false;
946 };
947 propagate_local_cancel(&inner, reason);
948 true
949 }
950}
951
952#[derive(Debug)]
962#[must_use = "futures do nothing unless polled or awaited"]
963pub struct LocalCancellation<'a> {
964 inner: &'a CxInner,
965 waiter_id: Option<u64>,
966 respect_mask: bool,
967}
968
969impl LocalCancellation<'_> {
970 fn unregister(&mut self) {
971 let Some(id) = self.waiter_id.take() else {
972 return;
973 };
974 let retired = {
975 let mut waiters = self
976 .inner
977 .local_cancel_waiters
978 .lock()
979 .unwrap_or_else(std::sync::PoisonError::into_inner);
980 waiters.remove(id)
981 };
982 drop(retired);
984 }
985}
986
987impl Future for LocalCancellation<'_> {
988 type Output = ();
989
990 fn poll(mut self: Pin<&mut Self>, task_cx: &mut TaskContext<'_>) -> Poll<Self::Output> {
991 let mut prepared_waker = None;
992 let this = self.as_mut().get_mut();
993 let inner = this.inner;
994
995 loop {
996 if local_cancellation_matches(inner, this.respect_mask) {
997 this.unregister();
998 drop(prepared_waker);
999 return Poll::Ready(());
1000 }
1001
1002 let dispatch = inner
1008 .cancel_dispatch_gate
1009 .lock()
1010 .unwrap_or_else(std::sync::PoisonError::into_inner);
1011 let mut waiters = inner
1012 .local_cancel_waiters
1013 .lock()
1014 .unwrap_or_else(std::sync::PoisonError::into_inner);
1015 if local_cancellation_matches(inner, this.respect_mask) {
1016 let retired = this.waiter_id.take().and_then(|id| waiters.remove(id));
1017 drop(waiters);
1018 drop(dispatch);
1019 drop(retired);
1020 drop(prepared_waker);
1021 return Poll::Ready(());
1022 }
1023
1024 let existing_position = this.waiter_id.and_then(|id| {
1025 waiters
1026 .entries
1027 .iter()
1028 .position(|registered| registered.id == id)
1029 });
1030 if existing_position
1031 .is_some_and(|position| waiters.entries[position].waker.will_wake(task_cx.waker()))
1032 {
1033 drop(waiters);
1034 drop(dispatch);
1035 drop(prepared_waker);
1036 return Poll::Pending;
1037 }
1038
1039 if existing_position.is_none()
1040 && let Err(error) = waiters.entries.try_reserve(1)
1041 {
1042 drop(waiters);
1043 drop(dispatch);
1044 drop(prepared_waker);
1045 panic!("failed to reserve local-cancellation waiter storage: {error}");
1046 }
1047
1048 let Some(new_waker) = prepared_waker.take() else {
1049 drop(waiters);
1052 drop(dispatch);
1053 prepared_waker = Some(task_cx.waker().clone());
1054 continue;
1055 };
1056
1057 let retired = if let Some(position) = existing_position {
1058 Some(std::mem::replace(
1059 &mut waiters.entries[position].waker,
1060 new_waker,
1061 ))
1062 } else {
1063 let id = waiters.allocate_id();
1064 waiters.entries.push(LocalCancelWaiter {
1065 id,
1066 waker: new_waker,
1067 });
1068 this.waiter_id = Some(id);
1069 None
1070 };
1071 drop(waiters);
1072 drop(dispatch);
1073 drop(retired);
1076 return Poll::Pending;
1077 }
1078 }
1079}
1080
1081impl Drop for LocalCancellation<'_> {
1082 fn drop(&mut self) {
1083 self.unregister();
1084 }
1085}
1086
1087#[derive(Debug)]
1093pub struct Cx<Caps: cap::SubsetOf<cap::All> = FullCaps> {
1094 inner: Arc<CxInner>,
1095 budget: Budget,
1096 trace_id: u64,
1097 decision_id: u64,
1098 policy_id: u64,
1099 _caps: PhantomData<fn() -> Caps>,
1101}
1102
1103impl<Caps: cap::SubsetOf<cap::All>> Clone for Cx<Caps> {
1104 fn clone(&self) -> Self {
1105 Self {
1106 inner: Arc::clone(&self.inner),
1107 budget: self.budget,
1108 trace_id: self.trace_id,
1109 decision_id: self.decision_id,
1110 policy_id: self.policy_id,
1111 _caps: PhantomData,
1112 }
1113 }
1114}
1115
1116impl Default for Cx<FullCaps> {
1117 fn default() -> Self {
1118 Self::new()
1119 }
1120}
1121
1122impl Cx<FullCaps> {
1123 #[must_use]
1124 pub fn new() -> Self {
1125 Self::with_budget(Budget::INFINITE)
1126 }
1127
1128 #[must_use]
1145 pub fn detached_rebind() -> Self {
1146 Self::new()
1147 }
1148}
1149
1150impl<Caps: cap::SubsetOf<cap::All>> Cx<Caps> {
1151 #[cfg(all(feature = "native", test))]
1152 #[must_use]
1153 #[allow(dead_code)]
1154 fn effective_native_cx(&self) -> NativeCx {
1155 let native = {
1156 let _dispatch = self
1157 .inner
1158 .cancel_dispatch_gate
1159 .lock()
1160 .unwrap_or_else(std::sync::PoisonError::into_inner);
1161 let attached_native = self
1162 .inner
1163 .attached_native_cx
1164 .lock()
1165 .unwrap_or_else(std::sync::PoisonError::into_inner)
1166 .as_ref()
1167 .cloned();
1168 attached_native.unwrap_or_else(|| {
1169 self.inner
1170 .fallback_native_cx
1171 .get_or_init(|| {
1172 NativeCx::for_request_with_budget(native_budget_from_local_at(
1173 self.budget,
1174 asupersync::time::wall_now(),
1175 ))
1176 })
1177 .clone()
1178 })
1179 };
1180 sync_one_native_cx_cancel(&self.inner, &native);
1184 native
1185 }
1186
1187 #[cfg(feature = "native")]
1188 #[must_use]
1189 fn native_cx_for_checkpoint(&self) -> Option<NativeCx> {
1190 let attached_native = self
1191 .inner
1192 .attached_native_cx
1193 .lock()
1194 .unwrap_or_else(std::sync::PoisonError::into_inner)
1195 .as_ref()
1196 .cloned();
1197 attached_native.or_else(|| self.inner.fallback_native_cx.get().cloned())
1198 }
1199
1200 #[must_use]
1201 pub fn with_budget(budget: Budget) -> Self {
1202 Self::with_budget_and_cancel_dispatch(budget, Arc::new(Mutex::new(())))
1203 }
1204
1205 fn with_budget_and_cancel_dispatch(
1206 budget: Budget,
1207 cancel_dispatch_gate: Arc<Mutex<()>>,
1208 ) -> Self {
1209 Self {
1210 inner: Arc::new(CxInner::new(cancel_dispatch_gate)),
1211 budget,
1212 trace_id: 0,
1213 decision_id: 0,
1214 policy_id: 0,
1215 _caps: PhantomData,
1216 }
1217 }
1218
1219 #[must_use]
1220 pub fn budget(&self) -> Budget {
1221 self.budget
1222 }
1223
1224 #[must_use]
1230 pub fn trace_id(&self) -> u64 {
1231 self.trace_id
1232 }
1233
1234 #[must_use]
1236 pub fn decision_id(&self) -> u64 {
1237 self.decision_id
1238 }
1239
1240 #[must_use]
1242 pub fn policy_id(&self) -> u64 {
1243 self.policy_id
1244 }
1245
1246 #[must_use]
1250 pub fn with_trace_context(mut self, trace_id: u64, decision_id: u64, policy_id: u64) -> Self {
1251 self.trace_id = trace_id;
1252 self.decision_id = decision_id;
1253 self.policy_id = policy_id;
1254 self
1255 }
1256
1257 #[must_use]
1261 pub fn with_decision_id(mut self, decision_id: u64) -> Self {
1262 self.decision_id = decision_id;
1263 self
1264 }
1265
1266 #[must_use]
1268 pub fn with_policy_id(mut self, policy_id: u64) -> Self {
1269 self.policy_id = policy_id;
1270 self
1271 }
1272
1273 #[must_use]
1279 pub fn scope_with_budget(&self, child: Budget) -> Self {
1280 Self {
1281 inner: Arc::clone(&self.inner),
1282 budget: self.budget.meet(child),
1283 trace_id: self.trace_id,
1284 decision_id: self.decision_id,
1285 policy_id: self.policy_id,
1286 _caps: PhantomData,
1287 }
1288 }
1289
1290 #[must_use]
1292 pub fn cleanup_scope(&self) -> Self {
1293 self.scope_with_budget(Budget::MINIMAL)
1294 }
1295
1296 #[must_use]
1300 pub fn restrict<NewCaps>(&self) -> Cx<NewCaps>
1301 where
1302 NewCaps: cap::SubsetOf<cap::All> + cap::SubsetOf<Caps>,
1303 {
1304 self.retype()
1305 }
1306
1307 #[must_use]
1309 fn retype<NewCaps>(&self) -> Cx<NewCaps>
1310 where
1311 NewCaps: cap::SubsetOf<cap::All>,
1312 {
1313 Cx {
1314 inner: Arc::clone(&self.inner),
1315 budget: self.budget,
1316 trace_id: self.trace_id,
1317 decision_id: self.decision_id,
1318 policy_id: self.policy_id,
1319 _caps: PhantomData,
1320 }
1321 }
1322
1323 #[must_use]
1328 pub fn is_cancel_requested(&self) -> bool {
1329 self.inner.cancel_requested.load(Ordering::Acquire)
1330 }
1331
1332 pub fn wait_for_local_cancellation(&self) -> LocalCancellation<'_> {
1338 LocalCancellation {
1339 inner: &self.inner,
1340 waiter_id: None,
1341 respect_mask: true,
1342 }
1343 }
1344
1345 pub fn wait_for_local_cancel_request(&self) -> LocalCancellation<'_> {
1351 LocalCancellation {
1352 inner: &self.inner,
1353 waiter_id: None,
1354 respect_mask: false,
1355 }
1356 }
1357
1358 pub fn cancel(&self) {
1362 self.cancel_with_reason(CancelReason::UserInterrupt);
1363 }
1364
1365 pub fn cancel_with_reason(&self, reason: CancelReason) {
1372 propagate_cancel(&self.inner, reason);
1373 }
1374
1375 #[must_use]
1377 pub fn cancel_state(&self) -> CancelState {
1378 *self
1379 .inner
1380 .cancel_state
1381 .lock()
1382 .unwrap_or_else(std::sync::PoisonError::into_inner)
1383 }
1384
1385 #[must_use]
1387 pub fn cancel_reason(&self) -> Option<CancelReason> {
1388 *self
1389 .inner
1390 .cancel_reason
1391 .lock()
1392 .unwrap_or_else(std::sync::PoisonError::into_inner)
1393 }
1394
1395 pub fn transition_to_running(&self) {
1397 let mut state = self
1398 .inner
1399 .cancel_state
1400 .lock()
1401 .unwrap_or_else(std::sync::PoisonError::into_inner);
1402 if *state == CancelState::Created {
1403 *state = CancelState::Running;
1404 }
1405 }
1406
1407 pub fn transition_to_finalizing(&self) {
1409 let mut state = self
1410 .inner
1411 .cancel_state
1412 .lock()
1413 .unwrap_or_else(std::sync::PoisonError::into_inner);
1414 if *state == CancelState::Cancelling {
1415 *state = CancelState::Finalizing;
1416 }
1417 }
1418
1419 pub fn transition_to_completed(&self) {
1421 let mut state = self
1422 .inner
1423 .cancel_state
1424 .lock()
1425 .unwrap_or_else(std::sync::PoisonError::into_inner);
1426 if matches!(*state, CancelState::Finalizing | CancelState::Running) {
1427 *state = CancelState::Completed;
1428 }
1429 }
1430
1431 pub fn set_eprocess_oracle(&self, oracle: Arc<EProcessOracle>) {
1433 let _ = self.inner.eprocess_oracle.set(oracle);
1434 }
1435
1436 pub fn clear_eprocess_oracle(&self) {
1438 }
1441
1442 #[cfg(feature = "native")]
1444 pub fn set_native_cx(&self, native_cx: NativeCx) {
1445 let retired = {
1446 let _dispatch = self
1447 .inner
1448 .cancel_dispatch_gate
1449 .lock()
1450 .unwrap_or_else(std::sync::PoisonError::into_inner);
1451 let mut attached = self
1452 .inner
1453 .attached_native_cx
1454 .lock()
1455 .unwrap_or_else(std::sync::PoisonError::into_inner);
1456 attached.replace(native_cx.clone())
1457 };
1458 sync_one_native_cx_cancel(&self.inner, &native_cx);
1462 drop(retired);
1466 }
1467
1468 #[cfg(not(feature = "native"))]
1470 pub fn set_native_cx<T>(&self, _native_cx: T) {}
1471
1472 #[cfg(feature = "native")]
1474 #[must_use]
1475 pub fn attached_native_cx(&self) -> Option<NativeCx> {
1476 self.inner
1477 .attached_native_cx
1478 .lock()
1479 .unwrap_or_else(std::sync::PoisonError::into_inner)
1480 .clone()
1481 }
1482
1483 #[cfg(feature = "native")]
1491 #[must_use]
1492 pub fn native_spawn_budget(&self, native_cx: &NativeCx) -> NativeBudget {
1493 let local = native_budget_from_local_at(self.budget, native_cx.now_for_observability());
1494 native_cx.budget().meet(local)
1495 }
1496
1497 #[cfg(not(feature = "native"))]
1499 #[must_use]
1500 pub fn attached_native_cx(&self) -> Option<NativeCx> {
1501 None
1502 }
1503
1504 #[cfg(feature = "native")]
1506 pub fn clear_native_cx(&self) {
1507 let retired = {
1508 let _dispatch = self
1509 .inner
1510 .cancel_dispatch_gate
1511 .lock()
1512 .unwrap_or_else(std::sync::PoisonError::into_inner);
1513 self.inner
1514 .attached_native_cx
1515 .lock()
1516 .unwrap_or_else(std::sync::PoisonError::into_inner)
1517 .take()
1518 };
1519 drop(retired);
1521 }
1522
1523 pub fn mark_blocking_io_inline_safe(&self) {
1528 self.inner
1529 .blocking_io_inline_safe
1530 .store(true, Ordering::Release);
1531 }
1532
1533 #[must_use]
1536 pub fn blocking_io_inline_safe(&self) -> bool {
1537 self.inner.blocking_io_inline_safe.load(Ordering::Acquire)
1538 }
1539
1540 #[cfg(not(feature = "native"))]
1542 pub fn clear_native_cx(&self) {}
1543
1544 #[must_use]
1545 fn maybe_cancel_via_eprocess(&self) -> bool {
1546 let Some(oracle) = self.inner.eprocess_oracle.get() else {
1547 return false;
1548 };
1549 let decision = oracle.decision(self.budget.priority);
1550 self.record_eprocess_decision(decision.clone());
1551 tracing::debug!(
1552 target: "fsqlite::cx",
1553 event = "eprocess_checkpoint",
1554 trace_id = self.trace_id,
1555 decision_id = self.decision_id,
1556 policy_id = self.policy_id,
1557 priority = decision.priority,
1558 evalue = decision.snapshot.evalue,
1559 threshold = decision.snapshot.rejection_threshold,
1560 observations = decision.snapshot.observations,
1561 priority_threshold = decision.snapshot.priority_threshold,
1562 should_shed = decision.should_shed,
1563 signal = ?decision.snapshot.last_signal
1564 );
1565 if decision.should_shed {
1566 tracing::info!(
1567 target: "fsqlite::cx",
1568 event = "eprocess_shedding_triggered",
1569 trace_id = self.trace_id,
1570 decision_id = self.decision_id,
1571 policy_id = self.policy_id,
1572 priority = decision.priority,
1573 evalue = decision.snapshot.evalue,
1574 threshold = decision.snapshot.rejection_threshold,
1575 signal = ?decision.snapshot.last_signal
1576 );
1577 self.cancel_with_reason(CancelReason::Abort);
1578 return true;
1579 }
1580 false
1581 }
1582
1583 #[cfg(feature = "native")]
1584 #[must_use]
1585 fn maybe_cancel_via_native_cx(&self, masked: bool) -> bool {
1586 let Some(native) = self.native_cx_for_checkpoint() else {
1587 return false;
1588 };
1589
1590 if masked {
1591 if native.is_cancel_requested() {
1592 let reason = native
1593 .cancel_reason()
1594 .as_ref()
1595 .map_or(CancelReason::Timeout, native_reason_to_local);
1596 self.cancel_with_reason(reason);
1597 return true;
1598 }
1599 return false;
1600 }
1601
1602 if native.checkpoint().is_err() {
1603 let reason = native
1604 .cancel_reason()
1605 .as_ref()
1606 .map_or(CancelReason::Timeout, native_reason_to_local);
1607 self.cancel_with_reason(reason);
1608 return true;
1609 }
1610 false
1611 }
1612
1613 pub fn checkpoint(&self) -> Result<()> {
1633 let cancel_requested = self.inner.cancel_requested.load(Ordering::Acquire);
1634 if !cancel_requested {
1635 if !self.maybe_cancel_via_eprocess() {
1638 #[cfg(feature = "native")]
1639 {
1640 let masked = self.inner.mask_depth.load(Ordering::Acquire) > 0;
1641 if !self.maybe_cancel_via_native_cx(masked) {
1642 return Ok(());
1643 }
1644 }
1645 #[cfg(not(feature = "native"))]
1646 {
1647 return Ok(());
1648 }
1649 }
1650 }
1651
1652 let masked = self.inner.mask_depth.load(Ordering::Acquire) > 0;
1655 if masked {
1656 return Ok(());
1657 }
1658
1659 {
1661 let mut state = self
1662 .inner
1663 .cancel_state
1664 .lock()
1665 .unwrap_or_else(std::sync::PoisonError::into_inner);
1666 if *state == CancelState::CancelRequested {
1667 *state = CancelState::Cancelling;
1668 }
1669 }
1670 Err(Error::cancelled())
1671 }
1672
1673 pub fn checkpoint_with(&self, msg: impl Into<String>) -> Result<()> {
1675 {
1676 let mut guard = self
1677 .inner
1678 .last_checkpoint_msg
1679 .lock()
1680 .unwrap_or_else(std::sync::PoisonError::into_inner);
1681 *guard = Some(msg.into());
1682 }
1683 self.checkpoint()
1684 }
1685
1686 #[must_use]
1687 pub fn last_checkpoint_message(&self) -> Option<String> {
1688 self.inner
1689 .last_checkpoint_msg
1690 .lock()
1691 .unwrap_or_else(std::sync::PoisonError::into_inner)
1692 .clone()
1693 }
1694
1695 #[must_use]
1697 pub fn last_eprocess_decision(&self) -> Option<EProcessDecision> {
1698 self.inner
1699 .last_eprocess_decision
1700 .lock()
1701 .unwrap_or_else(std::sync::PoisonError::into_inner)
1702 .clone()
1703 }
1704
1705 #[must_use]
1707 pub fn last_eprocess_snapshot(&self) -> Option<EProcessSnapshot> {
1708 self.last_eprocess_decision()
1709 .map(|decision| decision.snapshot)
1710 }
1711
1712 fn record_eprocess_decision(&self, decision: EProcessDecision) {
1713 *self
1714 .inner
1715 .last_eprocess_decision
1716 .lock()
1717 .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(decision);
1718 }
1719
1720 #[must_use]
1733 pub fn masked(&self) -> MaskGuard<'_> {
1734 let prev = self.inner.mask_depth.fetch_add(1, Ordering::AcqRel);
1735 if prev >= MAX_MASK_DEPTH {
1736 self.inner.mask_depth.fetch_sub(1, Ordering::Release);
1737 assert!(
1738 prev < MAX_MASK_DEPTH,
1739 "MAX_MASK_DEPTH ({MAX_MASK_DEPTH}) exceeded: mask nesting depth would be {}",
1740 prev + 1
1741 );
1742 }
1743 MaskGuard { inner: &self.inner }
1744 }
1745
1746 #[must_use]
1748 pub fn mask_depth(&self) -> u32 {
1749 self.inner.mask_depth.load(Ordering::Acquire)
1750 }
1751
1752 pub fn commit_section<R>(
1761 &self,
1762 poll_quota: u32,
1763 body: impl FnOnce(&CommitCtx) -> R,
1764 finalizer: impl FnOnce(),
1765 ) -> R {
1766 struct FinGuard<G: FnOnce()>(Option<G>);
1767 impl<G: FnOnce()> Drop for FinGuard<G> {
1768 fn drop(&mut self) {
1769 if let Some(f) = self.0.take() {
1770 f();
1771 }
1772 }
1773 }
1774
1775 let _mask = self.masked();
1776 let _fin = FinGuard(Some(finalizer));
1777 let ctx = CommitCtx::new(poll_quota);
1778 body(&ctx)
1779 }
1780
1781 #[must_use]
1789 pub fn create_child(&self) -> Self {
1790 self.create_child_with_runtime_affinity(true)
1791 }
1792
1793 #[must_use]
1802 pub fn create_child_for_spawn(&self) -> Self {
1803 self.create_child_with_runtime_affinity(false)
1804 }
1805
1806 fn create_child_with_runtime_affinity(&self, inherit_runtime_affinity: bool) -> Self {
1807 let mut child = Self::with_budget_and_cancel_dispatch(
1808 self.budget,
1809 Arc::clone(&self.inner.cancel_dispatch_gate),
1810 );
1811 child.trace_id = self.trace_id;
1812 child.decision_id = self.decision_id;
1813 child.policy_id = self.policy_id;
1814 if self.inner.unix_millis_is_fixed.load(Ordering::Acquire) {
1815 let unix_millis = self.inner.unix_millis.load(Ordering::Acquire);
1816 child
1817 .inner
1818 .unix_millis
1819 .store(unix_millis, Ordering::Release);
1820 child
1821 .inner
1822 .unix_millis_is_fixed
1823 .store(true, Ordering::Release);
1824 }
1825 if let Some(oracle) = self.inner.eprocess_oracle.get().cloned() {
1826 child.set_eprocess_oracle(oracle);
1827 }
1828 if inherit_runtime_affinity && self.blocking_io_inline_safe() {
1832 child.mark_blocking_io_inline_safe();
1833 }
1834
1835 #[cfg(feature = "native")]
1836 let native_to_sync = {
1837 let _dispatch = self
1838 .inner
1839 .cancel_dispatch_gate
1840 .lock()
1841 .unwrap_or_else(std::sync::PoisonError::into_inner);
1842 let attached_native = inherit_runtime_affinity
1843 .then(|| {
1844 self.inner
1845 .attached_native_cx
1846 .lock()
1847 .unwrap_or_else(std::sync::PoisonError::into_inner)
1848 .clone()
1849 })
1850 .flatten();
1851 if let Some(native) = attached_native.as_ref() {
1852 *child
1853 .inner
1854 .attached_native_cx
1855 .lock()
1856 .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(native.clone());
1857 }
1858
1859 let local_reason = self.cancel_reason();
1860 let native_reason = mirrored_native_cancel_reason(&self.inner);
1861 if let Some(local_reason) = local_reason.or(native_reason) {
1862 publish_cancel_state(&child.inner, local_reason, native_reason);
1863 }
1864
1865 let mut children = self
1868 .inner
1869 .children
1870 .lock()
1871 .unwrap_or_else(std::sync::PoisonError::into_inner);
1872 if children.len() == children.capacity() {
1873 children.retain(|registered| registered.strong_count() > 0);
1874 }
1875 children.push(Arc::downgrade(&child.inner));
1876
1877 attached_native.filter(|_| native_reason.is_some())
1878 };
1879
1880 #[cfg(not(feature = "native"))]
1881 {
1882 let _dispatch = self
1883 .inner
1884 .cancel_dispatch_gate
1885 .lock()
1886 .unwrap_or_else(std::sync::PoisonError::into_inner);
1887 if let Some(reason) = self.cancel_reason() {
1888 publish_cancel_state(&child.inner, reason, None);
1889 }
1890 let mut children = self
1891 .inner
1892 .children
1893 .lock()
1894 .unwrap_or_else(std::sync::PoisonError::into_inner);
1895 if children.len() == children.capacity() {
1896 children.retain(|registered| registered.strong_count() > 0);
1897 }
1898 children.push(Arc::downgrade(&child.inner));
1899 }
1900
1901 #[cfg(feature = "native")]
1902 if let Some(native) = native_to_sync {
1903 sync_one_native_cx_cancel(&child.inner, &native);
1904 }
1905
1906 child
1907 }
1908
1909 pub fn create_child_with_local_cancel_relay(&self) -> (Self, LocalCancelRelay) {
1917 let child = self.create_child();
1918 let relay = LocalCancelRelay {
1919 inner: Arc::downgrade(&child.inner),
1920 };
1921 (child, relay)
1922 }
1923
1924 pub fn set_unix_millis_for_testing(&self, millis: u64)
1926 where
1927 Caps: cap::HasTime,
1928 {
1929 self.inner.unix_millis.store(millis, Ordering::Release);
1930 self.inner
1931 .unix_millis_is_fixed
1932 .store(true, Ordering::Release);
1933 }
1934
1935 #[must_use]
1940 pub fn current_time_unix_millis(&self) -> u64
1941 where
1942 Caps: cap::HasTime,
1943 {
1944 if self.inner.unix_millis_is_fixed.load(Ordering::Acquire) {
1945 return self.inner.unix_millis.load(Ordering::Acquire);
1946 }
1947
1948 u64::try_from(
1949 SystemTime::now()
1950 .duration_since(SystemTime::UNIX_EPOCH)
1951 .unwrap_or_default()
1952 .as_millis(),
1953 )
1954 .unwrap_or(u64::MAX)
1955 }
1956
1957 #[must_use]
1959 pub fn current_time_julian_day(&self) -> f64
1960 where
1961 Caps: cap::HasTime,
1962 {
1963 let millis = self.current_time_unix_millis();
1964 #[allow(clippy::cast_precision_loss)]
1965 let secs = (millis as f64) / 1000.0;
1966 2_440_587.5 + (secs / 86_400.0)
1968 }
1969}
1970
1971#[derive(Debug)]
1979pub struct MaskGuard<'a> {
1980 inner: &'a CxInner,
1981}
1982
1983impl Drop for MaskGuard<'_> {
1984 fn drop(&mut self) {
1985 let previous = self.inner.mask_depth.fetch_sub(1, Ordering::AcqRel);
1986 debug_assert!(previous > 0, "mask depth underflow");
1987 if previous == 1 {
1988 let waiters = {
1989 let _dispatch = self
1990 .inner
1991 .cancel_dispatch_gate
1992 .lock()
1993 .unwrap_or_else(std::sync::PoisonError::into_inner);
1994 if self.inner.mask_depth.load(Ordering::Acquire) == 0
1995 && self.inner.cancel_requested.load(Ordering::Acquire)
1996 {
1997 take_local_cancel_waiters(self.inner)
1998 } else {
1999 Vec::new()
2000 }
2001 };
2002 let mut first_panic = None;
2003 dispatch_local_cancel_waiters(waiters, &mut first_panic);
2004 if let Some(payload) = first_panic {
2005 std::mem::forget(payload);
2008 }
2009 }
2010 }
2011}
2012
2013#[derive(Debug)]
2021pub struct CommitCtx {
2022 poll_remaining: AtomicU32,
2023}
2024
2025impl CommitCtx {
2026 fn new(poll_quota: u32) -> Self {
2027 Self {
2028 poll_remaining: AtomicU32::new(poll_quota),
2029 }
2030 }
2031
2032 #[must_use]
2034 pub fn poll_remaining(&self) -> u32 {
2035 self.poll_remaining.load(Ordering::Acquire)
2036 }
2037
2038 pub fn tick(&self) -> bool {
2040 let prev = self.poll_remaining.load(Ordering::Acquire);
2041 if prev == 0 {
2042 return false;
2043 }
2044 self.poll_remaining.fetch_sub(1, Ordering::AcqRel);
2045 true
2046 }
2047}
2048
2049#[cfg(test)]
2050mod tests {
2051 use super::*;
2052 use crate::eprocess::{EProcessConfig, EProcessSignal};
2053 use std::path::{Path, PathBuf};
2054 use std::sync::atomic::{AtomicBool, AtomicUsize};
2055 use std::sync::{Arc, Barrier, Weak};
2056
2057 #[derive(Debug, Default)]
2058 struct CountingWake(AtomicUsize);
2059
2060 impl std::task::Wake for CountingWake {
2061 fn wake(self: Arc<Self>) {
2062 self.0.fetch_add(1, Ordering::AcqRel);
2063 }
2064
2065 fn wake_by_ref(self: &Arc<Self>) {
2066 self.0.fetch_add(1, Ordering::AcqRel);
2067 }
2068 }
2069
2070 #[derive(Debug)]
2071 struct DescendantStateProbeWake {
2072 descendant: Weak<CxInner>,
2073 wake_count: AtomicUsize,
2074 saw_descendant_cancelled: AtomicBool,
2075 dispatch_gate_was_unlocked: AtomicBool,
2076 }
2077
2078 impl DescendantStateProbeWake {
2079 fn observe(&self) {
2080 let descendant = self
2081 .descendant
2082 .upgrade()
2083 .expect("observed descendant should remain alive");
2084 self.saw_descendant_cancelled.store(
2085 descendant.cancel_requested.load(Ordering::Acquire),
2086 Ordering::Release,
2087 );
2088 let dispatch_guard = descendant
2089 .cancel_dispatch_gate
2090 .try_lock()
2091 .expect("cancellation callbacks must run outside the family phase gate");
2092 self.dispatch_gate_was_unlocked
2093 .store(true, Ordering::Release);
2094 drop(dispatch_guard);
2095 self.wake_count.fetch_add(1, Ordering::AcqRel);
2096 }
2097 }
2098
2099 impl std::task::Wake for DescendantStateProbeWake {
2100 fn wake(self: Arc<Self>) {
2101 self.observe();
2102 }
2103
2104 fn wake_by_ref(self: &Arc<Self>) {
2105 self.observe();
2106 }
2107 }
2108
2109 #[derive(Debug)]
2110 struct ReentrantFamilyWake {
2111 cx: Cx<FullCaps>,
2112 wake_count: AtomicUsize,
2113 child_inherited_cancellation: AtomicBool,
2114 }
2115
2116 impl ReentrantFamilyWake {
2117 fn exercise(&self) {
2118 self.cx.cancel_with_reason(CancelReason::Abort);
2119 let child = self.cx.create_child();
2120 self.child_inherited_cancellation.store(
2121 child.cancel_reason() == Some(CancelReason::Abort),
2122 Ordering::Release,
2123 );
2124 let mask = self.cx.masked();
2125 drop(mask);
2126 self.wake_count.fetch_add(1, Ordering::AcqRel);
2127 }
2128 }
2129
2130 impl std::task::Wake for ReentrantFamilyWake {
2131 fn wake(self: Arc<Self>) {
2132 self.exercise();
2133 }
2134
2135 fn wake_by_ref(self: &Arc<Self>) {
2136 self.exercise();
2137 }
2138 }
2139
2140 #[derive(Debug, Default)]
2141 struct PanicWake;
2142
2143 impl std::task::Wake for PanicWake {
2144 fn wake(self: Arc<Self>) {
2145 panic!("intentional cancellation-waker panic");
2146 }
2147
2148 fn wake_by_ref(self: &Arc<Self>) {
2149 panic!("intentional cancellation-waker panic");
2150 }
2151 }
2152
2153 #[derive(Debug)]
2154 struct RegistryProbeWake {
2155 inner: Weak<CxInner>,
2156 wake_count: AtomicUsize,
2157 registry_was_unlocked: AtomicBool,
2158 }
2159
2160 impl RegistryProbeWake {
2161 fn probe_registry(&self) {
2162 let inner = self
2163 .inner
2164 .upgrade()
2165 .expect("observed context should remain alive");
2166 let registry_guard = inner
2167 .local_cancel_waiters
2168 .try_lock()
2169 .expect("waker callbacks must run after releasing the waiter registry");
2170 self.registry_was_unlocked.store(true, Ordering::Release);
2171 drop(registry_guard);
2172 self.wake_count.fetch_add(1, Ordering::AcqRel);
2173 }
2174 }
2175
2176 impl std::task::Wake for RegistryProbeWake {
2177 fn wake(self: Arc<Self>) {
2178 self.probe_registry();
2179 }
2180
2181 fn wake_by_ref(self: &Arc<Self>) {
2182 self.probe_registry();
2183 }
2184 }
2185
2186 fn local_cancel_waiter_count<Caps: cap::SubsetOf<cap::All>>(cx: &Cx<Caps>) -> usize {
2187 cx.inner
2188 .local_cancel_waiters
2189 .lock()
2190 .unwrap_or_else(std::sync::PoisonError::into_inner)
2191 .entries
2192 .len()
2193 }
2194
2195 #[test]
2196 fn test_cx_checkpoint_observes_cancellation() {
2197 let cx = Cx::new();
2198 assert_eq!(local_cancel_waiter_count(&cx), 0);
2199 assert!(cx.checkpoint().is_ok());
2200 cx.cancel();
2201 assert_eq!(local_cancel_waiter_count(&cx), 0);
2202 let err = cx.checkpoint().unwrap_err();
2203 assert_eq!(err.kind(), ErrorKind::Cancelled);
2204 assert_eq!(err.sqlite_error_code(), SQLITE_INTERRUPT);
2205 }
2206
2207 #[test]
2208 fn test_cx_capability_narrowing_compiles() {
2209 let cx = Cx::<FullCaps>::new();
2210 let _compute = cx.restrict::<ComputeCaps>();
2211 let _storage = cx.restrict::<StorageCaps>();
2212 }
2213
2214 #[test]
2215 fn test_cx_budget_meet_tightens() {
2216 let parent = Budget::INFINITE.with_deadline(Duration::from_millis(100));
2217 let child = Budget::INFINITE.with_deadline(Duration::from_millis(200));
2218 let effective = parent.meet(child);
2219 assert_eq!(effective.deadline, Some(Duration::from_millis(100)));
2220 }
2221
2222 #[test]
2223 fn test_cx_budget_priority_join() {
2224 let parent = Budget::INFINITE.with_priority(2);
2225 let child = Budget::INFINITE.with_priority(5);
2226 let effective = parent.meet(child);
2227 assert_eq!(effective.priority, 5);
2228 }
2229
2230 #[cfg(feature = "native")]
2231 #[test]
2232 fn bd_2jpu6_2_native_budget_translation_uses_supplied_clock_domain() {
2233 let now = NativeTime::from_nanos(1_000);
2234 let local = Budget::INFINITE.with_deadline(Duration::from_nanos(250));
2235
2236 let native = native_budget_from_local_at(local, now);
2237
2238 assert_eq!(native.deadline, Some(NativeTime::from_nanos(1_250)));
2239 }
2240
2241 #[cfg(feature = "native")]
2242 #[test]
2243 fn bd_2jpu6_2_native_spawn_budget_meets_parent_bounds_and_priority() {
2244 let parent = NativeBudget::INFINITE
2245 .with_poll_quota(80)
2246 .with_cost_quota(900)
2247 .with_priority(9);
2248 let native_cx = NativeCx::for_testing_with_budget(parent);
2249 let local = Cx::<FullCaps>::with_budget(
2250 Budget::INFINITE
2251 .with_poll_quota(60)
2252 .with_cost_quota(700)
2253 .with_priority(3),
2254 );
2255
2256 let effective = local.native_spawn_budget(&native_cx);
2257
2258 assert_eq!(effective.poll_quota, 60);
2259 assert_eq!(effective.cost_quota, Some(700));
2260 assert_eq!(effective.priority, 9);
2261 }
2262
2263 #[test]
2264 fn test_cx_scope_with_budget_cannot_loosen() {
2265 let cx =
2266 Cx::<FullCaps>::with_budget(Budget::INFINITE.with_deadline(Duration::from_millis(50)));
2267 let child = Budget::INFINITE.with_deadline(Duration::from_millis(100));
2268 let scoped = cx.scope_with_budget(child);
2269 assert_eq!(scoped.budget().deadline, Some(Duration::from_millis(50)));
2270 }
2271
2272 #[test]
2273 fn test_cx_checkpoint_with_message_records_message() {
2274 let cx = Cx::new();
2275 assert!(cx.checkpoint_with("vdbe pc=5").is_ok());
2276 assert_eq!(cx.last_checkpoint_message().as_deref(), Some("vdbe pc=5"));
2277 }
2278
2279 #[test]
2280 fn test_cx_cleanup_uses_minimal_budget() {
2281 let cx = Cx::<FullCaps>::with_budget(Budget::INFINITE.with_poll_quota(10_000));
2282 let cleanup = cx.cleanup_scope();
2283 assert_eq!(cleanup.budget(), Budget::MINIMAL);
2284 }
2285
2286 #[test]
2287 fn test_cx_restrict_storage_to_compute() {
2288 let cx = Cx::<FullCaps>::new();
2289 let storage = cx.restrict::<StorageCaps>();
2290 let _compute = storage.restrict::<ComputeCaps>();
2291 }
2292
2293 #[test]
2294 fn test_cx_restrict_is_zero_cost() {
2295 assert_eq!(
2298 std::mem::size_of::<Cx<FullCaps>>(),
2299 std::mem::size_of::<Cx<ComputeCaps>>()
2300 );
2301 }
2302
2303 #[test]
2304 fn test_budget_mixed_lattice() {
2305 let a = Budget {
2306 deadline: Some(Duration::from_millis(100)),
2307 poll_quota: 500,
2308 cost_quota: Some(1000),
2309 priority: 2,
2310 };
2311 let b = Budget {
2312 deadline: Some(Duration::from_millis(200)),
2313 poll_quota: 300,
2314 cost_quota: Some(2000),
2315 priority: 5,
2316 };
2317 let m = a.meet(b);
2318 assert_eq!(m.deadline, Some(Duration::from_millis(100)));
2320 assert_eq!(m.poll_quota, 300);
2321 assert_eq!(m.cost_quota, Some(1000));
2322 assert_eq!(m.priority, 5);
2324 }
2325
2326 #[test]
2327 fn test_budget_meet_commutative() {
2328 let a = Budget {
2329 deadline: Some(Duration::from_millis(50)),
2330 poll_quota: 400,
2331 cost_quota: Some(800),
2332 priority: 3,
2333 };
2334 let b = Budget {
2335 deadline: Some(Duration::from_millis(150)),
2336 poll_quota: 200,
2337 cost_quota: None,
2338 priority: 7,
2339 };
2340 assert_eq!(a.meet(b), b.meet(a));
2341 }
2342
2343 #[test]
2344 fn test_budget_meet_associative() {
2345 let a = Budget::INFINITE
2346 .with_deadline(Duration::from_millis(50))
2347 .with_poll_quota(100)
2348 .with_priority(1);
2349 let b = Budget::INFINITE
2350 .with_deadline(Duration::from_millis(150))
2351 .with_poll_quota(200)
2352 .with_priority(5);
2353 let c = Budget::INFINITE
2354 .with_deadline(Duration::from_millis(75))
2355 .with_poll_quota(50)
2356 .with_priority(3);
2357 assert_eq!(a.meet(b).meet(c), a.meet(b.meet(c)));
2358 }
2359
2360 #[test]
2361 fn test_budget_minimal_is_stricter_than_normal() {
2362 let normal = Budget::INFINITE.with_poll_quota(10_000);
2363 let effective = normal.meet(Budget::MINIMAL);
2364 assert_eq!(effective.poll_quota, Budget::MINIMAL.poll_quota);
2365 }
2366
2367 #[test]
2368 fn test_cx_cancel_shared_across_clones() {
2369 let cx1 = Cx::<FullCaps>::new();
2370 let cx2 = cx1.clone();
2371 assert!(!cx2.is_cancel_requested());
2372 cx1.cancel();
2373 assert!(cx2.is_cancel_requested());
2374 assert!(cx2.checkpoint().is_err());
2375 }
2376
2377 #[test]
2378 fn test_cx_cancel_shared_across_restrict() {
2379 let cx = Cx::<FullCaps>::new();
2380 let compute = cx.restrict::<ComputeCaps>();
2381 cx.cancel();
2382 assert!(compute.checkpoint().is_err());
2383 }
2384
2385 fn system_time_unix_millis() -> u64 {
2386 u64::try_from(
2387 SystemTime::now()
2388 .duration_since(SystemTime::UNIX_EPOCH)
2389 .unwrap_or_default()
2390 .as_millis(),
2391 )
2392 .unwrap_or(u64::MAX)
2393 }
2394
2395 #[test]
2396 fn test_cx_current_time_uses_live_clock_by_default() {
2397 let cx = Cx::<FullCaps>::new();
2398 let observed = cx.current_time_unix_millis();
2399 let expected = system_time_unix_millis();
2400
2401 assert!(
2402 observed.abs_diff(expected) <= 60_000,
2403 "default Cx clock must be live: observed={observed}, expected approximately {expected}"
2404 );
2405 }
2406
2407 #[test]
2408 fn test_cx_fixed_unix_millis_supports_full_u64_domain() {
2409 let cx = Cx::<FullCaps>::new();
2410
2411 cx.set_unix_millis_for_testing(0);
2412 assert_eq!(cx.current_time_unix_millis(), 0);
2413
2414 cx.set_unix_millis_for_testing(u64::MAX);
2415 assert_eq!(cx.current_time_unix_millis(), u64::MAX);
2416 }
2417
2418 #[test]
2419 fn test_cx_current_time_julian_day_uses_fixed_unix_millis() {
2420 let cx = Cx::<FullCaps>::new();
2421
2422 cx.set_unix_millis_for_testing(0);
2424 let jd = cx.current_time_julian_day();
2425 assert!((jd - 2_440_587.5).abs() < 1e-10);
2426
2427 cx.set_unix_millis_for_testing(86_400_000);
2429 let jd = cx.current_time_julian_day();
2430 assert!((jd - 2_440_588.5).abs() < 1e-10);
2431 }
2432
2433 #[test]
2434 fn test_cx_children_inherit_fixed_or_live_clock_state() {
2435 let fixed_parent = Cx::<FullCaps>::new();
2436 fixed_parent.set_unix_millis_for_testing(0);
2437 let fixed_child = fixed_parent.create_child();
2438 let fixed_spawn_child = fixed_parent.create_child_for_spawn();
2439
2440 assert_eq!(fixed_child.current_time_unix_millis(), 0);
2441 assert_eq!(fixed_spawn_child.current_time_unix_millis(), 0);
2442
2443 fixed_parent.set_unix_millis_for_testing(86_400_000);
2446 assert_eq!(fixed_child.current_time_unix_millis(), 0);
2447
2448 let live_parent = Cx::<FullCaps>::new();
2449 let live_child = live_parent.create_child();
2450 let observed = live_child.current_time_unix_millis();
2451 let expected = system_time_unix_millis();
2452 assert!(
2453 observed.abs_diff(expected) <= 60_000,
2454 "child of live Cx must remain live: observed={observed}, expected approximately {expected}"
2455 );
2456 }
2457
2458 #[test]
2459 fn test_cx_fixed_clock_updates_publish_complete_values() {
2460 const FIRST: u64 = 0xAAAA_AAAA_AAAA_AAAA;
2461 const SECOND: u64 = 0x5555_5555_5555_5555;
2462
2463 let cx = Cx::<FullCaps>::new();
2464 cx.set_unix_millis_for_testing(FIRST);
2465 let writer_cx = cx.clone();
2466 let writer = std::thread::spawn(move || {
2467 for _ in 0..1_000 {
2468 writer_cx.set_unix_millis_for_testing(FIRST);
2469 writer_cx.set_unix_millis_for_testing(SECOND);
2470 }
2471 });
2472
2473 for _ in 0..1_000 {
2474 let observed = cx.current_time_unix_millis();
2475 assert!(matches!(observed, FIRST | SECOND));
2476 }
2477 writer.join().expect("clock writer must not panic");
2478 assert_eq!(cx.current_time_unix_millis(), SECOND);
2479 }
2480
2481 #[test]
2482 fn test_capset_is_zero_sized() {
2483 assert_eq!(std::mem::size_of::<cap::All>(), 0);
2484 assert_eq!(std::mem::size_of::<cap::None>(), 0);
2485 assert_eq!(
2486 std::mem::size_of::<cap::CapSet<true, false, true, false, true>>(),
2487 0
2488 );
2489 }
2490
2491 #[test]
2492 fn test_cx_checkpoint_not_cancelled() {
2493 let cx = Cx::new();
2494 assert!(cx.checkpoint().is_ok());
2495 assert!(cx.checkpoint_with("still going").is_ok());
2496 }
2497
2498 #[test]
2499 fn test_cx_checkpoint_maps_to_sqlite_interrupt() {
2500 let cx = Cx::new();
2501 cx.cancel();
2502 let err = cx.checkpoint().unwrap_err();
2503 assert_eq!(err.sqlite_error_code(), SQLITE_INTERRUPT);
2504 }
2505
2506 #[test]
2507 fn test_cx_checkpoint_eprocess_sheds_low_priority_context() {
2508 let cx = Cx::<FullCaps>::with_budget(Budget::INFINITE.with_priority(3));
2509 let oracle = Arc::new(EProcessOracle::new(
2510 EProcessConfig {
2511 p0: 0.1,
2512 lambda: 5.0,
2513 alpha: 0.05,
2514 max_evalue: 1e12,
2515 },
2516 1,
2517 ));
2518 let signal = EProcessSignal::new(1.0, 1.0, 1.0);
2519 oracle.observe_signal(signal);
2520 oracle.observe_signal(signal);
2521 cx.set_eprocess_oracle(oracle);
2522 let err = cx.checkpoint().unwrap_err();
2523 assert_eq!(err.kind(), ErrorKind::Cancelled);
2524 assert_eq!(cx.cancel_reason(), Some(CancelReason::Abort));
2525 let decision = cx
2526 .last_eprocess_decision()
2527 .expect("checkpoint should record an e-process decision");
2528 assert!(decision.should_shed);
2529 assert_eq!(decision.snapshot.last_signal, Some(signal));
2530 }
2531
2532 #[test]
2533 fn test_cx_checkpoint_eprocess_respects_priority_threshold() {
2534 let cx = Cx::<FullCaps>::with_budget(Budget::INFINITE.with_priority(1));
2535 let oracle = Arc::new(EProcessOracle::new(
2536 EProcessConfig {
2537 p0: 0.1,
2538 lambda: 5.0,
2539 alpha: 0.05,
2540 max_evalue: 1e12,
2541 },
2542 1,
2543 ));
2544 let signal = EProcessSignal::new(1.0, 1.0, 1.0);
2545 oracle.observe_signal(signal);
2546 oracle.observe_signal(signal);
2547 cx.set_eprocess_oracle(oracle);
2548 assert!(cx.checkpoint().is_ok());
2549 assert!(!cx.is_cancel_requested());
2550 let decision = cx
2551 .last_eprocess_decision()
2552 .expect("checkpoint should still record non-shedding decisions");
2553 assert!(!decision.should_shed);
2554 assert_eq!(decision.priority, 1);
2555 assert_eq!(decision.snapshot.last_signal, Some(signal));
2556 }
2557
2558 #[test]
2559 fn test_cx_checkpoint_eprocess_preserves_masking_semantics() {
2560 let cx = Cx::<FullCaps>::with_budget(Budget::INFINITE.with_priority(3));
2561 let oracle = Arc::new(EProcessOracle::new(
2562 EProcessConfig {
2563 p0: 0.1,
2564 lambda: 5.0,
2565 alpha: 0.05,
2566 max_evalue: 1e12,
2567 },
2568 1,
2569 ));
2570 let signal = EProcessSignal::new(1.0, 1.0, 1.0);
2571 oracle.observe_signal(signal);
2572 oracle.observe_signal(signal);
2573 cx.set_eprocess_oracle(oracle);
2574 {
2575 let _mask = cx.masked();
2576 assert!(cx.checkpoint().is_ok());
2577 assert!(cx.is_cancel_requested());
2578 assert_eq!(cx.cancel_state(), CancelState::CancelRequested);
2579 assert_eq!(
2580 cx.last_eprocess_snapshot()
2581 .expect("checkpoint should record the masked decision")
2582 .last_signal,
2583 Some(signal)
2584 );
2585 }
2586 let err = cx.checkpoint().unwrap_err();
2587 assert_eq!(err.kind(), ErrorKind::Cancelled);
2588 }
2589
2590 #[test]
2591 fn test_create_child_inherits_eprocess_oracle() {
2592 let parent = Cx::<FullCaps>::with_budget(Budget::INFINITE.with_priority(3));
2593 let oracle = Arc::new(EProcessOracle::new(
2594 EProcessConfig {
2595 p0: 0.1,
2596 lambda: 5.0,
2597 alpha: 0.05,
2598 max_evalue: 1e12,
2599 },
2600 1,
2601 ));
2602 let signal = EProcessSignal::new(1.0, 1.0, 1.0);
2603 oracle.observe_signal(signal);
2604 oracle.observe_signal(signal);
2605 parent.set_eprocess_oracle(oracle);
2606
2607 let child = parent.create_child();
2608 let err = child.checkpoint().unwrap_err();
2609 assert_eq!(err.kind(), ErrorKind::Cancelled);
2610 assert_eq!(child.cancel_reason(), Some(CancelReason::Abort));
2611 assert_eq!(
2612 child
2613 .last_eprocess_snapshot()
2614 .expect("child checkpoint should record inherited oracle decision")
2615 .last_signal,
2616 Some(signal)
2617 );
2618 }
2619
2620 #[test]
2621 fn spawn_child_preserves_logical_context_without_thread_affinity() {
2622 let budget = Budget {
2623 deadline: Some(Duration::from_secs(7)),
2624 poll_quota: 123,
2625 cost_quota: Some(456),
2626 priority: 3,
2627 };
2628 let parent = Cx::<FullCaps>::with_budget(budget).with_trace_context(50, 60, 70);
2629 let oracle = Arc::new(EProcessOracle::new(
2630 EProcessConfig {
2631 p0: 0.1,
2632 lambda: 5.0,
2633 alpha: 0.05,
2634 max_evalue: 1e12,
2635 },
2636 1,
2637 ));
2638 parent.set_eprocess_oracle(Arc::clone(&oracle));
2639 parent.mark_blocking_io_inline_safe();
2640
2641 let child = parent.create_child_for_spawn();
2642 assert_eq!(child.budget(), budget);
2643 assert_eq!(child.trace_id(), 50);
2644 assert_eq!(child.decision_id(), 60);
2645 assert_eq!(child.policy_id(), 70);
2646 assert!(
2647 Arc::ptr_eq(
2648 child
2649 .inner
2650 .eprocess_oracle
2651 .get()
2652 .expect("spawn child should inherit the e-process oracle"),
2653 &oracle
2654 ),
2655 "spawn child must retain the exact logical policy oracle"
2656 );
2657 assert!(
2658 !child.blocking_io_inline_safe(),
2659 "spawn child must not inherit an OS-thread-only I/O permission"
2660 );
2661
2662 parent.cancel_with_reason(CancelReason::RegionClose);
2663 assert_eq!(child.cancel_reason(), Some(CancelReason::RegionClose));
2664 }
2665
2666 #[test]
2667 fn test_create_child_inherits_preexisting_parent_cancellation() {
2668 let parent = Cx::<FullCaps>::new();
2669 parent.cancel_with_reason(CancelReason::RegionClose);
2670
2671 let child = parent.create_child();
2672 assert_eq!(child.cancel_reason(), Some(CancelReason::RegionClose));
2673 assert_eq!(child.cancel_state(), CancelState::CancelRequested);
2674
2675 let err = child.checkpoint().unwrap_err();
2676 assert_eq!(err.kind(), ErrorKind::Cancelled);
2677 }
2678
2679 #[test]
2680 fn local_cancel_relay_is_subtree_scoped_and_reason_monotone() {
2681 let root = Cx::<FullCaps>::new();
2682 let sibling = root.create_child();
2683 let (operation, relay) = root.create_child_with_local_cancel_relay();
2684 let existing_descendant = operation.create_child();
2685
2686 assert!(relay.cancel_local(CancelReason::Timeout));
2687 assert!(relay.cancel_local(CancelReason::Abort));
2688 assert!(relay.cancel_local(CancelReason::UserInterrupt));
2689
2690 assert_eq!(operation.cancel_reason(), Some(CancelReason::Abort));
2691 assert_eq!(
2692 existing_descendant.cancel_reason(),
2693 Some(CancelReason::Abort)
2694 );
2695 assert!(operation.checkpoint().is_err());
2696 assert!(existing_descendant.checkpoint().is_err());
2697
2698 assert!(root.checkpoint().is_ok());
2699 assert!(sibling.checkpoint().is_ok());
2700 assert!(!root.is_cancel_requested());
2701 assert!(!sibling.is_cancel_requested());
2702
2703 let late_descendant = operation.create_child();
2704 assert_eq!(late_descendant.cancel_reason(), Some(CancelReason::Abort));
2705 assert!(late_descendant.checkpoint().is_err());
2706 assert!(root.checkpoint().is_ok());
2707 }
2708
2709 #[test]
2710 fn local_cancel_relay_is_weak_and_cross_thread_safe() {
2711 fn assert_send_sync<T: Send + Sync>() {}
2712 assert_send_sync::<LocalCancelRelay>();
2713
2714 let root = Cx::<FullCaps>::new();
2715 let (operation, relay) = root.create_child_with_local_cancel_relay();
2716 let cancel_thread =
2717 std::thread::spawn(move || relay.cancel_local(CancelReason::RegionClose));
2718 assert!(
2719 cancel_thread
2720 .join()
2721 .expect("cancel thread should not panic"),
2722 "live operation should accept a relayed cancellation"
2723 );
2724 assert_eq!(operation.cancel_reason(), Some(CancelReason::RegionClose));
2725
2726 let (dropped_operation, dropped_relay) = root.create_child_with_local_cancel_relay();
2727 drop(dropped_operation);
2728 assert!(
2729 !dropped_relay.cancel_local(CancelReason::Abort),
2730 "a weak relay must become inert after its target is dropped"
2731 );
2732 assert!(root.checkpoint().is_ok());
2733 }
2734
2735 #[test]
2736 fn local_cancellation_future_wakes_for_local_relay() {
2737 let root = Cx::<FullCaps>::new();
2738 let (operation, relay) = root.create_child_with_local_cancel_relay();
2739 let wake_count = Arc::new(CountingWake::default());
2740 let waker = Waker::from(Arc::clone(&wake_count));
2741 let mut task_cx = TaskContext::from_waker(&waker);
2742 let mut cancellation = std::pin::pin!(operation.wait_for_local_cancellation());
2743
2744 assert_eq!(
2745 cancellation.as_mut().poll(&mut task_cx),
2746 Poll::Pending,
2747 "uncancelled operation should register one waiter"
2748 );
2749 assert_eq!(local_cancel_waiter_count(&operation), 1);
2750
2751 assert!(relay.cancel_local(CancelReason::RegionClose));
2752 assert_eq!(
2753 wake_count.0.load(Ordering::Acquire),
2754 1,
2755 "local relay cancellation should wake the registered future"
2756 );
2757 assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Ready(()));
2758 assert_eq!(
2759 local_cancel_waiter_count(&operation),
2760 0,
2761 "ready future must leave no stale registration"
2762 );
2763 assert!(root.checkpoint().is_ok());
2764 }
2765
2766 #[test]
2767 fn dropping_local_cancellation_future_unregisters_waiter() {
2768 let cx = Cx::<FullCaps>::new();
2769 let wake_count = Arc::new(CountingWake::default());
2770 let waker = Waker::from(wake_count);
2771 let mut task_cx = TaskContext::from_waker(&waker);
2772
2773 {
2774 let mut cancellation = std::pin::pin!(cx.wait_for_local_cancellation());
2775 assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Pending);
2776 assert_eq!(local_cancel_waiter_count(&cx), 1);
2777 }
2778
2779 assert_eq!(
2780 local_cancel_waiter_count(&cx),
2781 0,
2782 "dropping a pending future must remove its waker"
2783 );
2784 }
2785
2786 #[test]
2787 fn already_cancelled_local_future_never_registers_a_waiter() {
2788 let cx = Cx::<FullCaps>::new();
2789 cx.cancel();
2790 assert_eq!(local_cancel_waiter_count(&cx), 0);
2791
2792 let wake_count = Arc::new(CountingWake::default());
2793 let waker = Waker::from(wake_count);
2794 let mut task_cx = TaskContext::from_waker(&waker);
2795 let mut cancellation = std::pin::pin!(cx.wait_for_local_cancellation());
2796 assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Ready(()));
2797 assert_eq!(
2798 local_cancel_waiter_count(&cx),
2799 0,
2800 "an already-ready first poll must not register a waiter"
2801 );
2802 }
2803
2804 #[test]
2805 fn repoll_replaces_the_registered_waker() {
2806 let cx = Cx::<FullCaps>::new();
2807 let first_wake_count = Arc::new(CountingWake::default());
2808 let first_waker = Waker::from(Arc::clone(&first_wake_count));
2809 let mut first_task_cx = TaskContext::from_waker(&first_waker);
2810 let second_wake_count = Arc::new(CountingWake::default());
2811 let second_waker = Waker::from(Arc::clone(&second_wake_count));
2812 let mut second_task_cx = TaskContext::from_waker(&second_waker);
2813 let mut cancellation = std::pin::pin!(cx.wait_for_local_cancellation());
2814
2815 assert_eq!(
2816 cancellation.as_mut().poll(&mut first_task_cx),
2817 Poll::Pending
2818 );
2819 assert_eq!(
2820 cancellation.as_mut().poll(&mut second_task_cx),
2821 Poll::Pending
2822 );
2823 assert_eq!(local_cancel_waiter_count(&cx), 1);
2824
2825 cx.cancel();
2826 assert_eq!(
2827 first_wake_count.0.load(Ordering::Acquire),
2828 0,
2829 "a replaced waker must not be invoked"
2830 );
2831 assert_eq!(
2832 second_wake_count.0.load(Ordering::Acquire),
2833 1,
2834 "only the most recently registered waker should be invoked"
2835 );
2836 assert_eq!(
2837 cancellation.as_mut().poll(&mut second_task_cx),
2838 Poll::Ready(())
2839 );
2840 }
2841
2842 #[test]
2843 fn local_cancellation_waker_runs_outside_the_registry_lock() {
2844 let cx = Cx::<FullCaps>::new();
2845 let probe = Arc::new(RegistryProbeWake {
2846 inner: Arc::downgrade(&cx.inner),
2847 wake_count: AtomicUsize::new(0),
2848 registry_was_unlocked: AtomicBool::new(false),
2849 });
2850 let waker = Waker::from(Arc::clone(&probe));
2851 let mut task_cx = TaskContext::from_waker(&waker);
2852 let mut cancellation = std::pin::pin!(cx.wait_for_local_cancellation());
2853
2854 assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Pending);
2855 cx.cancel();
2856 assert!(
2857 probe.registry_was_unlocked.load(Ordering::Acquire),
2858 "wake callback should acquire the registry without reentrant deadlock"
2859 );
2860 assert_eq!(probe.wake_count.load(Ordering::Acquire), 1);
2861 assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Ready(()));
2862 }
2863
2864 #[test]
2865 fn cancellation_publishes_the_complete_subtree_before_waking_observers() {
2866 let root = Cx::<FullCaps>::new();
2867 let descendant = root.create_child();
2868 let probe = Arc::new(DescendantStateProbeWake {
2869 descendant: Arc::downgrade(&descendant.inner),
2870 wake_count: AtomicUsize::new(0),
2871 saw_descendant_cancelled: AtomicBool::new(false),
2872 dispatch_gate_was_unlocked: AtomicBool::new(false),
2873 });
2874 let waker = Waker::from(Arc::clone(&probe));
2875 let mut task_cx = TaskContext::from_waker(&waker);
2876 let mut cancellation = std::pin::pin!(root.wait_for_local_cancellation());
2877
2878 assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Pending);
2879 root.cancel();
2880
2881 assert_eq!(probe.wake_count.load(Ordering::Acquire), 1);
2882 assert!(
2883 probe.saw_descendant_cancelled.load(Ordering::Acquire),
2884 "reentrant observers must never see a half-published cancellation tree"
2885 );
2886 assert!(
2887 probe.dispatch_gate_was_unlocked.load(Ordering::Acquire),
2888 "callbacks must be able to re-enter family cancellation machinery"
2889 );
2890 assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Ready(()));
2891 assert!(descendant.checkpoint().is_err());
2892 }
2893
2894 #[test]
2895 fn panicking_cancellation_waker_does_not_suppress_other_observers() {
2896 let root = Cx::<FullCaps>::new();
2897 let descendant = root.create_child();
2898 let panic_waker = Waker::from(Arc::new(PanicWake));
2899 let mut panic_task_cx = TaskContext::from_waker(&panic_waker);
2900 let wake_count = Arc::new(CountingWake::default());
2901 let counting_waker = Waker::from(Arc::clone(&wake_count));
2902 let mut counting_task_cx = TaskContext::from_waker(&counting_waker);
2903 let mut panicking = std::pin::pin!(root.wait_for_local_cancellation());
2904 let mut counting = std::pin::pin!(root.wait_for_local_cancellation());
2905
2906 assert_eq!(panicking.as_mut().poll(&mut panic_task_cx), Poll::Pending);
2907 assert_eq!(counting.as_mut().poll(&mut counting_task_cx), Poll::Pending);
2908 let cancel_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2909 root.cancel();
2910 }));
2911
2912 assert!(
2913 cancel_result.is_err(),
2914 "the first callback panic must be resumed after notification completes"
2915 );
2916 assert_eq!(
2917 wake_count.0.load(Ordering::Acquire),
2918 1,
2919 "one panicking observer must not suppress later observers"
2920 );
2921 assert!(descendant.checkpoint().is_err());
2922 assert_eq!(panicking.as_mut().poll(&mut panic_task_cx), Poll::Ready(()));
2923 assert_eq!(
2924 counting.as_mut().poll(&mut counting_task_cx),
2925 Poll::Ready(())
2926 );
2927 assert_eq!(local_cancel_waiter_count(&root), 0);
2928 }
2929
2930 #[test]
2931 fn cancellation_waker_can_reenter_family_state_without_deadlock() {
2932 let root = Cx::<FullCaps>::new();
2933 let probe = Arc::new(ReentrantFamilyWake {
2934 cx: root.clone(),
2935 wake_count: AtomicUsize::new(0),
2936 child_inherited_cancellation: AtomicBool::new(false),
2937 });
2938 let waker = Waker::from(Arc::clone(&probe));
2939 let mut task_cx = TaskContext::from_waker(&waker);
2940 let mut cancellation = std::pin::pin!(root.wait_for_local_cancellation());
2941
2942 assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Pending);
2943 root.cancel();
2944
2945 assert_eq!(probe.wake_count.load(Ordering::Acquire), 1);
2946 assert!(
2947 probe.child_inherited_cancellation.load(Ordering::Acquire),
2948 "a child created reentrantly must be initialized before it is linked"
2949 );
2950 assert_eq!(root.cancel_reason(), Some(CancelReason::Abort));
2951 assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Ready(()));
2952 }
2953
2954 #[test]
2955 fn repeated_local_waiter_poll_and_drop_accumulates_nothing() {
2956 let cx = Cx::<FullCaps>::new();
2957 let initial_children = cx
2958 .inner
2959 .children
2960 .lock()
2961 .unwrap_or_else(std::sync::PoisonError::into_inner)
2962 .len();
2963 let wake_count = Arc::new(CountingWake::default());
2964 let waker = Waker::from(wake_count);
2965 let mut task_cx = TaskContext::from_waker(&waker);
2966
2967 for _ in 0..256 {
2968 let mut cancellation = std::pin::pin!(cx.wait_for_local_cancellation());
2969 assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Pending);
2970 }
2971
2972 assert_eq!(
2973 local_cancel_waiter_count(&cx),
2974 0,
2975 "dropped local wait futures must not accumulate registry entries"
2976 );
2977 assert_eq!(
2978 cx.inner
2979 .children
2980 .lock()
2981 .unwrap_or_else(std::sync::PoisonError::into_inner)
2982 .len(),
2983 initial_children,
2984 "local wait futures must not allocate child contexts"
2985 );
2986 }
2987
2988 #[test]
2989 fn local_cancellation_future_defers_while_masked_and_wakes_on_unmask() {
2990 let root = Cx::<FullCaps>::new();
2991 let (operation, relay) = root.create_child_with_local_cancel_relay();
2992 let mask = operation.masked();
2993 let wake_count = Arc::new(CountingWake::default());
2994 let waker = Waker::from(Arc::clone(&wake_count));
2995 let mut task_cx = TaskContext::from_waker(&waker);
2996 let mut cancellation = std::pin::pin!(operation.wait_for_local_cancellation());
2997
2998 assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Pending);
2999 assert!(relay.cancel_local(CancelReason::Abort));
3000 assert_eq!(wake_count.0.load(Ordering::Acquire), 1);
3001
3002 assert_eq!(
3003 cancellation.as_mut().poll(&mut task_cx),
3004 Poll::Pending,
3005 "masking must defer cancellation observation"
3006 );
3007 assert_eq!(
3008 local_cancel_waiter_count(&operation),
3009 1,
3010 "a masked future must remain registered for the unmask boundary"
3011 );
3012
3013 drop(mask);
3014 assert_eq!(
3015 wake_count.0.load(Ordering::Acquire),
3016 2,
3017 "outermost unmask must wake a deferred cancellation observer"
3018 );
3019 assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Ready(()));
3020 }
3021
3022 #[test]
3023 fn local_cancel_request_future_is_ready_while_masked() {
3024 let root = Cx::<FullCaps>::new();
3025 let (operation, relay) = root.create_child_with_local_cancel_relay();
3026 let mask = operation.masked();
3027 let wake_count = Arc::new(CountingWake::default());
3028 let waker = Waker::from(Arc::clone(&wake_count));
3029 let mut task_cx = TaskContext::from_waker(&waker);
3030 let mut request = std::pin::pin!(operation.wait_for_local_cancel_request());
3031
3032 assert_eq!(request.as_mut().poll(&mut task_cx), Poll::Pending);
3033 assert!(relay.cancel_local(CancelReason::UserInterrupt));
3034 assert_eq!(wake_count.0.load(Ordering::Acquire), 1);
3035 assert_eq!(
3036 request.as_mut().poll(&mut task_cx),
3037 Poll::Ready(()),
3038 "raw request notification must not reinterpret masking policy"
3039 );
3040 assert!(
3041 operation.checkpoint().is_ok(),
3042 "the context checkpoint itself must continue to defer while masked"
3043 );
3044 drop(mask);
3045 assert!(operation.checkpoint().is_err());
3046 }
3047
3048 #[test]
3049 fn local_cancellation_registration_race_leaves_no_stale_waiter() {
3050 for _ in 0..64 {
3051 let root = Cx::<FullCaps>::new();
3052 let (operation, relay) = root.create_child_with_local_cancel_relay();
3053 let barrier = Arc::new(Barrier::new(2));
3054 let cancel_barrier = Arc::clone(&barrier);
3055 let cancel_thread = std::thread::spawn(move || {
3056 cancel_barrier.wait();
3057 relay.cancel_local(CancelReason::UserInterrupt)
3058 });
3059
3060 let wake_count = Arc::new(CountingWake::default());
3061 let waker = Waker::from(Arc::clone(&wake_count));
3062 let mut task_cx = TaskContext::from_waker(&waker);
3063 let mut cancellation = std::pin::pin!(operation.wait_for_local_cancellation());
3064 barrier.wait();
3065 let first_poll = cancellation.as_mut().poll(&mut task_cx);
3066
3067 assert!(
3068 cancel_thread
3069 .join()
3070 .expect("cancel thread should not panic")
3071 );
3072 if first_poll == Poll::Pending {
3073 assert!(
3074 wake_count.0.load(Ordering::Acquire) > 0,
3075 "a waiter registered during cancellation must be notified"
3076 );
3077 }
3078 assert_eq!(cancellation.as_mut().poll(&mut task_cx), Poll::Ready(()));
3079 assert_eq!(
3080 local_cancel_waiter_count(&operation),
3081 0,
3082 "registration/cancellation race must not strand a waker"
3083 );
3084 }
3085 }
3086
3087 #[test]
3088 fn local_cancel_relay_handles_deep_subtrees_on_a_small_stack() {
3089 let root = Cx::<FullCaps>::new();
3090 let (operation, relay) = root.create_child_with_local_cancel_relay();
3091 let mut chain = Vec::with_capacity(8_193);
3092 chain.push(operation);
3093 for _ in 0..8_192 {
3094 let child = chain
3095 .last()
3096 .expect("chain must contain its root")
3097 .create_child();
3098 chain.push(child);
3099 }
3100
3101 let cancel_thread = std::thread::Builder::new()
3102 .name("local-cancel-deep-tree".to_owned())
3103 .stack_size(256 * 1024)
3104 .spawn(move || relay.cancel_local(CancelReason::RegionClose))
3105 .expect("small-stack cancellation thread should spawn");
3106 assert!(
3107 cancel_thread
3108 .join()
3109 .expect("iterative cancellation traversal must not overflow")
3110 );
3111 assert_eq!(
3112 chain.last().and_then(Cx::cancel_reason),
3113 Some(CancelReason::RegionClose)
3114 );
3115 assert!(root.checkpoint().is_ok());
3116 }
3117
3118 #[cfg(feature = "native")]
3119 #[test]
3120 fn test_cx_checkpoint_native_cx_cancellation_maps_reason() {
3121 let cx = Cx::<FullCaps>::new();
3122 let native = NativeCx::for_testing();
3123 cx.set_native_cx(native.clone());
3124 native.set_cancel_reason(NativeCancelReason::timeout());
3125
3126 let err = cx.checkpoint().unwrap_err();
3127 assert_eq!(err.kind(), ErrorKind::Cancelled);
3128 assert_eq!(cx.cancel_reason(), Some(CancelReason::Timeout));
3129 }
3130
3131 #[cfg(feature = "native")]
3132 #[test]
3133 fn test_cx_cancel_reason_propagates_to_native_cx() {
3134 let cx = Cx::<FullCaps>::new();
3135 let native = NativeCx::for_testing();
3136 cx.set_native_cx(native.clone());
3137
3138 cx.cancel_with_reason(CancelReason::RegionClose);
3139 let reason = native
3140 .cancel_reason()
3141 .expect("native cancel reason must be set");
3142 assert_eq!(reason.kind, NativeCancelKind::ParentCancelled);
3143 }
3144
3145 #[cfg(feature = "native")]
3146 #[test]
3147 fn local_cancel_relay_preserves_shared_native_context_for_late_children() {
3148 let root = Cx::<FullCaps>::new();
3149 let native = NativeCx::for_testing();
3150 root.set_native_cx(native.clone());
3151 let sibling = root.create_child();
3152 let (operation, relay) = root.create_child_with_local_cancel_relay();
3153 let existing_descendant = operation.create_child();
3154
3155 assert!(relay.cancel_local(CancelReason::Abort));
3156 assert!(operation.checkpoint().is_err());
3157 assert!(existing_descendant.checkpoint().is_err());
3158 assert!(root.checkpoint().is_ok());
3159 assert!(sibling.checkpoint().is_ok());
3160 assert!(
3161 native.checkpoint().is_ok(),
3162 "local operation cancellation must not poison shared native I/O state"
3163 );
3164
3165 let late_descendant = operation.create_child();
3166 assert!(late_descendant.checkpoint().is_err());
3167 assert!(
3168 native.checkpoint().is_ok(),
3169 "a descendant created after local cancellation must inherit locally"
3170 );
3171
3172 root.cancel_with_reason(CancelReason::RegionClose);
3173 assert!(
3174 native.is_cancel_requested(),
3175 "later ordinary root cancellation must still cross the native boundary"
3176 );
3177 }
3178
3179 #[cfg(feature = "native")]
3180 #[test]
3181 fn local_cancel_relay_preserves_native_contexts_attached_after_cancellation() {
3182 let root = Cx::<FullCaps>::new();
3183 let (operation, relay) = root.create_child_with_local_cancel_relay();
3184 assert!(relay.cancel_local(CancelReason::Abort));
3185
3186 let fallback = operation.effective_native_cx();
3187 assert!(
3188 fallback.checkpoint().is_ok(),
3189 "local cancellation must not taint a later fallback native context"
3190 );
3191
3192 let replacement = NativeCx::for_testing();
3193 operation.set_native_cx(replacement.clone());
3194 assert!(
3195 replacement.checkpoint().is_ok(),
3196 "local cancellation must not taint a later explicit native context"
3197 );
3198 assert!(operation.checkpoint().is_err());
3199 }
3200
3201 #[cfg(feature = "native")]
3202 #[test]
3203 fn local_reason_never_leaks_through_later_ordinary_cancellation() {
3204 let root = Cx::<FullCaps>::new();
3205 let shared_native = NativeCx::for_testing();
3206 root.set_native_cx(shared_native.clone());
3207 let (operation, relay) = root.create_child_with_local_cancel_relay();
3208
3209 assert!(relay.cancel_local(CancelReason::Abort));
3210 root.cancel_with_reason(CancelReason::Timeout);
3211
3212 assert_eq!(operation.cancel_reason(), Some(CancelReason::Abort));
3213 assert_eq!(
3214 shared_native
3215 .cancel_reason()
3216 .expect("ordinary cancellation must reach shared native")
3217 .kind,
3218 NativeCancelKind::Timeout,
3219 "the stronger local-only Abort must not cross the native boundary"
3220 );
3221
3222 let late_descendant = operation.create_child();
3223 assert_eq!(
3224 late_descendant.cancel_reason(),
3225 Some(CancelReason::Abort),
3226 "late descendants inherit the aggregate local reason"
3227 );
3228 assert_eq!(
3229 shared_native
3230 .cancel_reason()
3231 .expect("late-child registration must retain ordinary reason")
3232 .kind,
3233 NativeCancelKind::Timeout
3234 );
3235
3236 operation.clear_native_cx();
3237 let fallback = operation.effective_native_cx();
3238 assert_eq!(
3239 fallback
3240 .cancel_reason()
3241 .expect("late fallback must receive ordinary reason")
3242 .kind,
3243 NativeCancelKind::Timeout
3244 );
3245
3246 let replacement = NativeCx::for_testing();
3247 operation.set_native_cx(replacement.clone());
3248 assert_eq!(
3249 replacement
3250 .cancel_reason()
3251 .expect("late explicit attachment must receive ordinary reason")
3252 .kind,
3253 NativeCancelKind::Timeout
3254 );
3255 }
3256
3257 #[cfg(feature = "native")]
3258 #[test]
3259 fn weaker_ordinary_reason_cannot_downgrade_native_cancellation() {
3260 let cx = Cx::<FullCaps>::new();
3261 let native = NativeCx::for_testing();
3262 cx.set_native_cx(native.clone());
3263
3264 cx.cancel_with_reason(CancelReason::Abort);
3265 cx.cancel_with_reason(CancelReason::Timeout);
3266
3267 assert_eq!(cx.cancel_reason(), Some(CancelReason::Abort));
3268 assert_eq!(
3269 native
3270 .cancel_reason()
3271 .expect("native reason must remain present")
3272 .kind,
3273 NativeCancelKind::ResourceUnavailable
3274 );
3275 }
3276
3277 #[cfg(feature = "native")]
3278 #[test]
3279 fn native_attachment_racing_ordinary_cancellation_never_misses_reason() {
3280 for _ in 0..128 {
3281 let cx = Cx::<FullCaps>::new();
3282 let cancel_cx = cx.clone();
3283 let attach_cx = cx.clone();
3284 let native = NativeCx::for_testing();
3285 let attached_native = native.clone();
3286 let gate = Arc::new(std::sync::Barrier::new(3));
3287 let cancel_gate = Arc::clone(&gate);
3288 let attach_gate = Arc::clone(&gate);
3289
3290 let cancel_thread = std::thread::spawn(move || {
3291 cancel_gate.wait();
3292 cancel_cx.cancel_with_reason(CancelReason::RegionClose);
3293 });
3294 let attach_thread = std::thread::spawn(move || {
3295 attach_gate.wait();
3296 attach_cx.set_native_cx(attached_native);
3297 });
3298 gate.wait();
3299 cancel_thread.join().expect("cancellation must not panic");
3300 attach_thread.join().expect("attachment must not panic");
3301
3302 assert_eq!(
3303 native
3304 .cancel_reason()
3305 .expect("racing attachment must observe cancellation")
3306 .kind,
3307 NativeCancelKind::ParentCancelled
3308 );
3309 }
3310 }
3311
3312 #[cfg(feature = "native")]
3313 #[test]
3314 fn racing_native_replacements_each_synchronize_the_exact_supplied_handle() {
3315 for _ in 0..128 {
3316 let cx = Cx::<FullCaps>::new();
3317 cx.cancel_with_reason(CancelReason::RegionClose);
3318
3319 let native_a = NativeCx::for_testing();
3320 let native_b = NativeCx::for_testing();
3321 let setter_a = cx.clone();
3322 let setter_b = cx.clone();
3323 let supplied_a = native_a.clone();
3324 let supplied_b = native_b.clone();
3325 let gate = Arc::new(std::sync::Barrier::new(3));
3326 let gate_a = Arc::clone(&gate);
3327 let gate_b = Arc::clone(&gate);
3328
3329 let thread_a = std::thread::spawn(move || {
3330 gate_a.wait();
3331 setter_a.set_native_cx(supplied_a);
3332 });
3333 let thread_b = std::thread::spawn(move || {
3334 gate_b.wait();
3335 setter_b.set_native_cx(supplied_b);
3336 });
3337 gate.wait();
3338 thread_a.join().expect("first replacement must not panic");
3339 thread_b.join().expect("second replacement must not panic");
3340
3341 for native in [&native_a, &native_b] {
3342 assert_eq!(
3343 native
3344 .cancel_reason()
3345 .expect("each exact supplied handle must be synchronized")
3346 .kind,
3347 NativeCancelKind::ParentCancelled
3348 );
3349 }
3350 }
3351 }
3352
3353 #[cfg(feature = "native")]
3354 #[test]
3355 fn fallback_creation_racing_ordinary_cancellation_never_misses_reason() {
3356 for _ in 0..128 {
3357 let cx = Cx::<FullCaps>::new();
3358 let cancel_cx = cx.clone();
3359 let fallback_cx = cx.clone();
3360 let gate = Arc::new(std::sync::Barrier::new(3));
3361 let cancel_gate = Arc::clone(&gate);
3362 let fallback_gate = Arc::clone(&gate);
3363
3364 let cancel_thread = std::thread::spawn(move || {
3365 cancel_gate.wait();
3366 cancel_cx.cancel_with_reason(CancelReason::RegionClose);
3367 });
3368 let fallback_thread = std::thread::spawn(move || {
3369 fallback_gate.wait();
3370 fallback_cx.effective_native_cx()
3371 });
3372 gate.wait();
3373 cancel_thread.join().expect("cancellation must not panic");
3374 let native = fallback_thread
3375 .join()
3376 .expect("fallback creation must not panic");
3377
3378 assert_eq!(
3379 native
3380 .cancel_reason()
3381 .expect("racing fallback must observe cancellation")
3382 .kind,
3383 NativeCancelKind::ParentCancelled
3384 );
3385 }
3386 }
3387
3388 #[cfg(feature = "native")]
3389 #[test]
3390 fn ordinary_cancel_before_native_attachment_is_mirrored_after_registration() {
3391 let cx = Cx::<FullCaps>::new();
3392 cx.cancel_with_reason(CancelReason::RegionClose);
3393
3394 let native = NativeCx::for_testing();
3395 cx.set_native_cx(native.clone());
3396 let reason = native
3397 .cancel_reason()
3398 .expect("ordinary local cancellation must mirror to a later attachment");
3399 assert_eq!(reason.kind, NativeCancelKind::ParentCancelled);
3400 }
3401
3402 #[cfg(feature = "native")]
3403 #[test]
3404 fn test_cx_checkpoint_native_cx_respects_local_masking() {
3405 let cx = Cx::<FullCaps>::new();
3406 let native = NativeCx::for_testing();
3407 cx.set_native_cx(native.clone());
3408 native.set_cancel_reason(NativeCancelReason::user("cancel"));
3409
3410 {
3411 let _mask = cx.masked();
3412 assert!(cx.checkpoint().is_ok());
3413 assert!(cx.is_cancel_requested());
3414 assert_eq!(cx.cancel_state(), CancelState::CancelRequested);
3415 }
3416
3417 let err = cx.checkpoint().unwrap_err();
3418 assert_eq!(err.kind(), ErrorKind::Cancelled);
3419 }
3420
3421 #[cfg(feature = "native")]
3422 #[test]
3423 fn test_cx_effective_native_cx_uses_fallback_without_marking_explicit_attachment() {
3424 let cx = Cx::<FullCaps>::with_budget(Budget::INFINITE.with_priority(7));
3425
3426 assert!(cx.attached_native_cx().is_none());
3427 let native = cx.effective_native_cx();
3428 assert!(cx.attached_native_cx().is_none());
3429 assert!(native.checkpoint().is_ok());
3430 }
3431
3432 #[cfg(feature = "native")]
3433 #[test]
3434 fn test_cx_checkpoint_without_native_context_does_not_create_fallback() {
3435 let cx = Cx::<FullCaps>::new();
3436
3437 assert!(cx.inner.fallback_native_cx.get().is_none());
3438 assert!(cx.checkpoint().is_ok());
3439 assert!(cx.inner.fallback_native_cx.get().is_none());
3440 }
3441
3442 #[cfg(feature = "native")]
3443 #[test]
3444 fn test_cx_set_native_cx_replaces_fallback_context() {
3445 let cx = Cx::<FullCaps>::new();
3446 let _ = cx.effective_native_cx();
3447
3448 let replacement = NativeCx::for_testing();
3449 cx.set_native_cx(replacement.clone());
3450 replacement.set_cancel_reason(NativeCancelReason::timeout());
3451
3452 let err = cx.checkpoint().unwrap_err();
3453 assert_eq!(err.kind(), ErrorKind::Cancelled);
3454 assert_eq!(cx.cancel_reason(), Some(CancelReason::Timeout));
3455 }
3456
3457 #[cfg(feature = "native")]
3458 #[test]
3459 fn test_create_child_copies_preexisting_cancellation_into_fallback_native_cx() {
3460 let parent = Cx::<FullCaps>::new();
3461 parent.cancel_with_reason(CancelReason::RegionClose);
3462
3463 let child = parent.create_child();
3464 let reason = child
3465 .effective_native_cx()
3466 .cancel_reason()
3467 .expect("fallback native cx should mirror inherited cancellation");
3468 assert_eq!(reason.kind, NativeCancelKind::ParentCancelled);
3469 }
3470
3471 #[cfg(feature = "native")]
3472 #[test]
3473 fn test_create_child_inherits_explicit_native_cx_attachment() {
3474 let parent = Cx::<FullCaps>::new();
3475 let native = NativeCx::for_testing();
3476 parent.set_native_cx(native.clone());
3477
3478 let child = parent.create_child();
3479 assert!(child.attached_native_cx().is_some());
3480
3481 native.set_cancel_reason(NativeCancelReason::timeout());
3482 let err = child
3483 .checkpoint()
3484 .expect_err("child should observe inherited native cancel");
3485 assert_eq!(err.kind(), ErrorKind::Cancelled);
3486 assert_eq!(child.cancel_reason(), Some(CancelReason::Timeout));
3487 }
3488
3489 #[cfg(feature = "native")]
3490 #[test]
3491 fn spawn_child_does_not_carry_a_task_affine_native_context() {
3492 let parent = Cx::<FullCaps>::new();
3493 parent.set_native_cx(NativeCx::for_testing());
3494 let child = parent.create_child_for_spawn();
3495
3496 assert!(
3497 child.attached_native_cx().is_none(),
3498 "spawn child must start without the caller task's native context"
3499 );
3500 assert!(
3501 child.inner.fallback_native_cx.get().is_none(),
3502 "spawn child must not invent a fallback context before task entry"
3503 );
3504
3505 let task_native = NativeCx::for_testing();
3506 child.set_native_cx(task_native);
3507 assert!(
3508 child.attached_native_cx().is_some(),
3509 "the spawned task must be able to attach its own native context"
3510 );
3511 }
3512
3513 #[test]
3514 fn test_budget_infinite_is_identity_for_meet() {
3515 let budget = Budget {
3516 deadline: Some(Duration::from_millis(42)),
3517 poll_quota: 500,
3518 cost_quota: Some(1000),
3519 priority: 7,
3520 };
3521 assert_eq!(budget.meet(Budget::INFINITE), budget);
3522 assert_eq!(Budget::INFINITE.meet(budget), budget);
3523 }
3524
3525 #[test]
3526 fn test_budget_none_constraints_propagate() {
3527 let a = Budget {
3528 deadline: None,
3529 poll_quota: u32::MAX,
3530 cost_quota: None,
3531 priority: 0,
3532 };
3533 let b = Budget {
3534 deadline: Some(Duration::from_millis(50)),
3535 poll_quota: 100,
3536 cost_quota: Some(500),
3537 priority: 3,
3538 };
3539 let m = a.meet(b);
3540 assert_eq!(m.deadline, Some(Duration::from_millis(50)));
3541 assert_eq!(m.poll_quota, 100);
3542 assert_eq!(m.cost_quota, Some(500));
3543 assert_eq!(m.priority, 3);
3544 }
3545
3546 #[test]
3547 fn test_cx_scope_budget_chains() {
3548 let cx = Cx::<FullCaps>::with_budget(
3549 Budget::INFINITE
3550 .with_deadline(Duration::from_millis(100))
3551 .with_poll_quota(1000),
3552 );
3553 let s1 = cx.scope_with_budget(Budget::INFINITE.with_deadline(Duration::from_millis(50)));
3555 assert_eq!(s1.budget().deadline, Some(Duration::from_millis(50)));
3556 assert_eq!(s1.budget().poll_quota, 1000);
3557
3558 let s2 = s1.scope_with_budget(Budget::INFINITE.with_poll_quota(200));
3560 assert_eq!(s2.budget().deadline, Some(Duration::from_millis(50)));
3561 assert_eq!(s2.budget().poll_quota, 200);
3562 }
3563
3564 fn collect_rs_files(dir: &Path, out: &mut Vec<PathBuf>) -> std::io::Result<()> {
3565 for entry in std::fs::read_dir(dir)? {
3566 let entry = entry?;
3567 let path = entry.path();
3568 if path.is_dir() {
3569 collect_rs_files(&path, out)?;
3570 } else if path.extension().is_some_and(|ext| ext == "rs") {
3571 out.push(path);
3572 }
3573 }
3574 Ok(())
3575 }
3576
3577 fn scan_file_outside_cfg_test_items(src: &str, patterns: &[&str]) -> Vec<(usize, String)> {
3578 let mut hits = Vec::new();
3579
3580 let mut brace_depth: i32 = 0;
3581 let mut pending_cfg_test = false;
3582 let mut pending_attr_paren_depth: i32 = 0;
3583 let mut skip_until_depth: Option<i32> = None;
3584
3585 for (idx, line) in src.lines().enumerate() {
3586 let trimmed = line.trim_start();
3587 let paren_delta = i32::try_from(line.matches('(').count()).unwrap_or(i32::MAX)
3588 - i32::try_from(line.matches(')').count()).unwrap_or(i32::MAX);
3589
3590 if skip_until_depth.is_none() {
3591 if trimmed.starts_with("#[cfg(test)]") && trimmed.contains('{') {
3593 pending_cfg_test = false;
3594 pending_attr_paren_depth = 0;
3595 skip_until_depth = Some(brace_depth);
3596 } else if trimmed.contains("fn test_") && trimmed.contains('{') {
3597 skip_until_depth = Some(brace_depth);
3598 } else if trimmed.starts_with("#[cfg(test)]") {
3599 pending_cfg_test = true;
3600 pending_attr_paren_depth = 0;
3601 } else if pending_cfg_test {
3602 if trimmed.starts_with("#[") || pending_attr_paren_depth > 0 {
3604 pending_attr_paren_depth =
3605 pending_attr_paren_depth.saturating_add(paren_delta);
3606 } else if trimmed.is_empty() || trimmed.starts_with("//") {
3607 } else if trimmed.contains('{') {
3609 pending_cfg_test = false;
3610 pending_attr_paren_depth = 0;
3611 skip_until_depth = Some(brace_depth);
3612 } else {
3613 pending_cfg_test = false;
3614 pending_attr_paren_depth = 0;
3615 }
3616 } else {
3617 for &pat in patterns {
3618 if line.contains(pat) {
3619 hits.push((idx + 1, pat.to_string()));
3620 }
3621 }
3622 }
3623 }
3624
3625 let opens = i32::try_from(line.matches('{').count()).unwrap_or(i32::MAX);
3627 let closes = i32::try_from(line.matches('}').count()).unwrap_or(i32::MAX);
3628 brace_depth = brace_depth.saturating_add(opens).saturating_sub(closes);
3629
3630 if let Some(until) = skip_until_depth
3631 && brace_depth <= until
3632 {
3633 skip_until_depth = None;
3634 }
3635 }
3636
3637 hits
3638 }
3639
3640 #[test]
3641 fn test_scan_file_outside_cfg_test_items_skips_cfg_test_functions_and_modules() {
3642 let src = r"
3643fn production_path() {
3644 let _ = Cx::new();
3645}
3646
3647#[cfg(test)]
3648fn test_only_helper() {
3649 let _ = Cx::new();
3650}
3651
3652#[cfg(test)]
3653mod tests {
3654 fn nested_test_helper() {
3655 let _ = Cx::default();
3656 }
3657}
3658";
3659
3660 let hits = scan_file_outside_cfg_test_items(src, &["Cx::new(", "Cx::default("]);
3661 assert_eq!(hits, vec![(3, "Cx::new(".to_string())]);
3662 }
3663
3664 #[test]
3665 fn test_no_direct_cx_constructors_in_runtime_production_code() {
3666 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
3667 let repo_root = manifest_dir
3668 .parent()
3669 .and_then(Path::parent)
3670 .expect("fsqlite-types manifest dir must be crates/<name>");
3671 let crates_dir = repo_root.join("crates");
3672 let runtime_crates = [
3673 "fsqlite-core",
3674 "fsqlite-vdbe",
3675 "fsqlite-btree",
3676 "fsqlite-pager",
3677 "fsqlite-wal",
3678 "fsqlite-mvcc",
3679 ];
3680 let forbidden = ["Cx::new(", "Cx::default("];
3681
3682 let mut violations: Vec<String> = Vec::new();
3683 let mut crate_dirs: Vec<PathBuf> = Vec::new();
3684 for entry in std::fs::read_dir(&crates_dir).expect("read crates/ dir") {
3685 let entry = entry.expect("read crates/ entry");
3686 let path = entry.path();
3687 if path.is_dir() {
3688 crate_dirs.push(path);
3689 }
3690 }
3691
3692 for crate_dir in crate_dirs {
3693 let crate_name = crate_dir
3694 .file_name()
3695 .and_then(|s| s.to_str())
3696 .unwrap_or("<unknown>");
3697 if !runtime_crates.contains(&crate_name) {
3698 continue;
3699 }
3700
3701 let src_dir = crate_dir.join("src");
3702 if !src_dir.is_dir() {
3703 continue;
3704 }
3705
3706 let mut files = Vec::new();
3707 collect_rs_files(&src_dir, &mut files).expect("collect rs files");
3708
3709 for file in files {
3710 if file
3711 .file_name()
3712 .and_then(|name| name.to_str())
3713 .is_some_and(|name| name.contains("test"))
3714 {
3715 continue;
3716 }
3717
3718 let src = std::fs::read_to_string(&file).expect("read file");
3719 let rel_path = file.strip_prefix(repo_root).unwrap_or(&file);
3720
3721 for (line, pat) in scan_file_outside_cfg_test_items(&src, &forbidden) {
3722 let line_text = src.lines().nth(line - 1).unwrap_or("").trim();
3723 violations.push(format!(
3731 "{crate_name}:{path}:{line} uses forbidden `{pat}` outside cfg(test) code: {line_text}",
3732 path = rel_path.display()
3733 ));
3734 }
3735 }
3736 }
3737
3738 assert!(
3739 violations.is_empty(),
3740 "direct `Cx::new()` / `Cx::default()` production-path violations:\n{}",
3741 violations.join("\n")
3742 );
3743 }
3744
3745 #[test]
3746 fn test_ambient_authority_audit_gate() {
3747 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
3750 let repo_root = manifest_dir
3751 .parent()
3752 .and_then(Path::parent)
3753 .expect("fsqlite-types manifest dir must be crates/<name>");
3754 let crates_dir = repo_root.join("crates");
3755
3756 let always_forbidden = [
3758 "SystemTime::now(",
3759 "Instant::now(",
3760 "thread_rng(",
3761 "getrandom",
3762 "std::net::",
3763 "std::thread::spawn",
3764 "tokio::spawn",
3765 ];
3766
3767 let non_vfs_forbidden = ["std::fs::"];
3769
3770 let exempt_crates = [
3801 "fsqlite-harness",
3802 "fsqlite-cli",
3803 "fsqlite-e2e",
3804 "fsqlite-observability",
3805 "fsqlite-core",
3806 "fsqlite-vdbe",
3807 "fsqlite-mvcc",
3808 "fsqlite-parser",
3809 "fsqlite-planner",
3810 "fsqlite-wal",
3811 "fsqlite-vfs",
3812 "fsqlite-types",
3813 "fsqlite-func",
3814 "fsqlite",
3815 "fsqlite-btree",
3816 "fsqlite-c-api",
3817 "fsqlite-pager",
3818 "beads-doctor",
3819 ];
3820
3821 let mut violations: Vec<String> = Vec::new();
3822 let mut crate_dirs: Vec<PathBuf> = Vec::new();
3823 for entry in std::fs::read_dir(&crates_dir).expect("read crates/ dir") {
3824 let entry = entry.expect("read crates/ entry");
3825 let path = entry.path();
3826 if path.is_dir() {
3827 crate_dirs.push(path);
3828 }
3829 }
3830
3831 for crate_dir in crate_dirs {
3832 let crate_name = crate_dir
3833 .file_name()
3834 .and_then(|s| s.to_str())
3835 .unwrap_or("<unknown>");
3836 if exempt_crates.contains(&crate_name) {
3837 continue;
3838 }
3839 let src_dir = crate_dir.join("src");
3840 if !src_dir.is_dir() {
3841 continue;
3842 }
3843
3844 let mut files = Vec::new();
3845 collect_rs_files(&src_dir, &mut files).expect("collect rs files");
3846
3847 for file in files {
3848 let src = std::fs::read_to_string(&file).expect("read file");
3849 for (line, pat) in scan_file_outside_cfg_test_items(&src, &always_forbidden) {
3850 violations.push(format!(
3851 "{crate_name}:{path}:{line} uses forbidden `{pat}`",
3852 path = file.display()
3853 ));
3854 }
3855
3856 if crate_name != "fsqlite-vfs" {
3857 for (line, pat) in scan_file_outside_cfg_test_items(&src, &non_vfs_forbidden) {
3858 violations.push(format!(
3859 "{crate_name}:{path}:{line} uses forbidden `{pat}` (non-vfs crate)",
3860 path = file.display()
3861 ));
3862 }
3863 }
3864 }
3865 }
3866
3867 assert!(
3868 violations.is_empty(),
3869 "ambient authority violations (outside cfg(test) modules):\n{}",
3870 violations.join("\n")
3871 );
3872 }
3873
3874 const BEAD_ID: &str = "bd-samf";
3879
3880 #[test]
3881 fn test_cancel_state_machine_all_transitions() {
3882 let cx = Cx::<FullCaps>::new();
3884 assert_eq!(
3885 cx.cancel_state(),
3886 CancelState::Created,
3887 "bead_id={BEAD_ID} initial_state"
3888 );
3889
3890 cx.transition_to_running();
3891 assert_eq!(
3892 cx.cancel_state(),
3893 CancelState::Running,
3894 "bead_id={BEAD_ID} after_start"
3895 );
3896
3897 cx.cancel_with_reason(CancelReason::UserInterrupt);
3898 assert_eq!(
3899 cx.cancel_state(),
3900 CancelState::CancelRequested,
3901 "bead_id={BEAD_ID} after_cancel"
3902 );
3903
3904 let err = cx.checkpoint();
3906 assert!(err.is_err(), "bead_id={BEAD_ID} checkpoint_returns_err");
3907 assert_eq!(
3908 cx.cancel_state(),
3909 CancelState::Cancelling,
3910 "bead_id={BEAD_ID} after_checkpoint_observation"
3911 );
3912
3913 cx.transition_to_finalizing();
3914 assert_eq!(
3915 cx.cancel_state(),
3916 CancelState::Finalizing,
3917 "bead_id={BEAD_ID} after_finalize_start"
3918 );
3919
3920 cx.transition_to_completed();
3921 assert_eq!(
3922 cx.cancel_state(),
3923 CancelState::Completed,
3924 "bead_id={BEAD_ID} after_complete"
3925 );
3926 }
3927
3928 #[test]
3929 fn test_cancel_propagates_to_children() {
3930 let parent = Cx::<FullCaps>::new();
3932 parent.transition_to_running();
3933
3934 let child1 = parent.create_child();
3935 child1.transition_to_running();
3936 let child2 = parent.create_child();
3937 child2.transition_to_running();
3938 let child3 = parent.create_child();
3939 child3.transition_to_running();
3940
3941 assert!(!child1.is_cancel_requested());
3942 assert!(!child2.is_cancel_requested());
3943 assert!(!child3.is_cancel_requested());
3944
3945 parent.cancel_with_reason(CancelReason::RegionClose);
3946
3947 assert!(
3949 child1.is_cancel_requested(),
3950 "bead_id={BEAD_ID} child1_cancelled"
3951 );
3952 assert!(
3953 child2.is_cancel_requested(),
3954 "bead_id={BEAD_ID} child2_cancelled"
3955 );
3956 assert!(
3957 child3.is_cancel_requested(),
3958 "bead_id={BEAD_ID} child3_cancelled"
3959 );
3960
3961 assert_eq!(child1.cancel_state(), CancelState::CancelRequested);
3963 assert_eq!(child2.cancel_state(), CancelState::CancelRequested);
3964 assert_eq!(child3.cancel_state(), CancelState::CancelRequested);
3965
3966 assert_eq!(child1.cancel_reason(), Some(CancelReason::RegionClose));
3968 }
3969
3970 #[test]
3971 fn test_dropped_children_are_pruned_from_parent_links() {
3972 let parent = Cx::<FullCaps>::new();
3973
3974 let live_child = parent.create_child();
3975 let dropped_child = parent.create_child();
3976 drop(dropped_child);
3977
3978 parent.cancel_with_reason(CancelReason::RegionClose);
3980
3981 let live_count = {
3982 let children = parent
3983 .inner
3984 .children
3985 .lock()
3986 .unwrap_or_else(std::sync::PoisonError::into_inner);
3987 children.iter().filter_map(Weak::upgrade).count()
3988 };
3989 assert_eq!(live_count, 1, "only the live child should remain linked");
3990 assert!(live_child.is_cancel_requested());
3991 }
3992
3993 #[test]
3994 fn dropped_children_are_pruned_before_registry_growth_without_cancellation() {
3995 let parent = Cx::<FullCaps>::new();
3996 drop(parent.create_child());
3997 let initial_capacity = parent
3998 .inner
3999 .children
4000 .lock()
4001 .unwrap_or_else(std::sync::PoisonError::into_inner)
4002 .capacity();
4003 assert!(initial_capacity > 0);
4004
4005 for _ in 0..4_096 {
4006 drop(parent.create_child());
4007 }
4008
4009 let children = parent
4010 .inner
4011 .children
4012 .lock()
4013 .unwrap_or_else(std::sync::PoisonError::into_inner);
4014 assert_eq!(
4015 children.capacity(),
4016 initial_capacity,
4017 "historical dead children must not grow an uncancelled family registry"
4018 );
4019 assert!(
4020 children.len() <= initial_capacity,
4021 "only the current bounded batch of dead weak links may remain"
4022 );
4023 }
4024
4025 #[test]
4026 fn test_cancel_idempotent_strongest_wins() {
4027 let cx = Cx::<FullCaps>::new();
4029 cx.transition_to_running();
4030
4031 cx.cancel_with_reason(CancelReason::Timeout);
4032 assert_eq!(
4033 cx.cancel_reason(),
4034 Some(CancelReason::Timeout),
4035 "bead_id={BEAD_ID} first_reason"
4036 );
4037
4038 cx.cancel_with_reason(CancelReason::Abort);
4040 assert_eq!(
4041 cx.cancel_reason(),
4042 Some(CancelReason::Abort),
4043 "bead_id={BEAD_ID} upgraded_reason"
4044 );
4045
4046 cx.cancel_with_reason(CancelReason::UserInterrupt);
4048 assert_eq!(
4049 cx.cancel_reason(),
4050 Some(CancelReason::Abort),
4051 "bead_id={BEAD_ID} reason_stays_strongest"
4052 );
4053 }
4054
4055 #[test]
4056 fn test_losers_drain_on_race() {
4057 use std::sync::atomic::AtomicBool;
4060
4061 let loser_cx = Cx::<FullCaps>::new();
4062 loser_cx.transition_to_running();
4063
4064 let obligation_resolved = Arc::new(AtomicBool::new(false));
4066 let ob_clone = Arc::clone(&obligation_resolved);
4067
4068 loser_cx.cancel_with_reason(CancelReason::RegionClose);
4070
4071 assert!(loser_cx.checkpoint().is_err());
4073 assert_eq!(loser_cx.cancel_state(), CancelState::Cancelling);
4074
4075 ob_clone.store(true, Ordering::Release);
4077 loser_cx.transition_to_finalizing();
4078 loser_cx.transition_to_completed();
4079
4080 assert!(
4081 obligation_resolved.load(Ordering::Acquire),
4082 "bead_id={BEAD_ID} loser_obligation_resolved"
4083 );
4084 assert_eq!(
4085 loser_cx.cancel_state(),
4086 CancelState::Completed,
4087 "bead_id={BEAD_ID} loser_drained"
4088 );
4089 }
4090
4091 #[test]
4092 fn test_vdbe_checkpoint_cancel_observed_at_next_opcode() {
4093 let cx = Cx::<FullCaps>::new();
4096 cx.transition_to_running();
4097
4098 let mut last_executed = 0u32;
4099 for opcode in 0..100u32 {
4100 if cx.checkpoint_with(format!("vdbe pc={opcode}")).is_err() {
4102 last_executed = opcode;
4103 break;
4104 }
4105 last_executed = opcode;
4107 if opcode == 50 {
4109 cx.cancel_with_reason(CancelReason::UserInterrupt);
4110 }
4111 }
4112
4113 assert_eq!(
4114 last_executed, 51,
4115 "bead_id={BEAD_ID} cancel_observed_at_opcode_51"
4116 );
4117 }
4118
4119 #[test]
4120 fn test_btree_checkpoint_cancel_within_one_node() {
4121 let cx = Cx::<FullCaps>::new();
4124 cx.transition_to_running();
4125
4126 let nodes = ["root", "internal_l", "internal_r", "leaf_a", "leaf_b"];
4127 let cancel_at = 2; let mut observed_at = None;
4129
4130 for (i, node) in nodes.iter().enumerate() {
4131 if cx.checkpoint_with(format!("btree node={node}")).is_err() {
4133 observed_at = Some(i);
4134 break;
4135 }
4136 if i == cancel_at {
4139 cx.cancel_with_reason(CancelReason::UserInterrupt);
4140 }
4141 }
4142
4143 assert_eq!(
4144 observed_at,
4145 Some(cancel_at + 1),
4146 "bead_id={BEAD_ID} btree_cancel_within_one_node"
4147 );
4148 }
4149
4150 #[test]
4151 fn test_masked_section_defers_cancel() {
4152 let cx = Cx::<FullCaps>::new();
4155 cx.transition_to_running();
4156
4157 cx.cancel_with_reason(CancelReason::UserInterrupt);
4158 assert!(cx.is_cancel_requested());
4159
4160 {
4162 let _guard = cx.masked();
4163 assert_eq!(cx.mask_depth(), 1);
4164
4165 assert!(
4167 cx.checkpoint().is_ok(),
4168 "bead_id={BEAD_ID} checkpoint_ok_while_masked"
4169 );
4170
4171 {
4173 let _inner = cx.masked();
4174 assert_eq!(cx.mask_depth(), 2);
4175 assert!(cx.checkpoint().is_ok());
4176 }
4177 assert_eq!(cx.mask_depth(), 1);
4178 }
4179 assert_eq!(cx.mask_depth(), 0);
4180
4181 assert!(
4183 cx.checkpoint().is_err(),
4184 "bead_id={BEAD_ID} checkpoint_err_after_mask_exit"
4185 );
4186 }
4187
4188 #[test]
4189 #[should_panic(expected = "MAX_MASK_DEPTH")]
4190 #[allow(clippy::collection_is_never_read)]
4191 fn test_max_mask_depth_exceeded_panics() {
4192 let cx = Cx::<FullCaps>::new();
4194 let mut guards = Vec::new();
4195 for _ in 0..MAX_MASK_DEPTH {
4196 guards.push(cx.masked());
4197 }
4198 let _overflow = cx.masked();
4200 }
4201
4202 #[test]
4203 fn test_commit_section_completes_under_cancel() {
4204 let cx = Cx::<FullCaps>::new();
4206 cx.transition_to_running();
4207
4208 let ops_completed = Arc::new(AtomicU32::new(0));
4209 let finalizer_ran = Arc::new(AtomicBool::new(false));
4210
4211 let ops = Arc::clone(&ops_completed);
4212 let fin = Arc::clone(&finalizer_ran);
4213
4214 cx.commit_section(
4215 10,
4216 |ctx| {
4217 assert!(ctx.tick());
4219 ops.fetch_add(1, Ordering::Release);
4220
4221 cx.cancel_with_reason(CancelReason::UserInterrupt);
4223
4224 assert!(ctx.tick());
4226 ops.fetch_add(1, Ordering::Release);
4227 assert!(
4228 cx.checkpoint().is_ok(),
4229 "bead_id={BEAD_ID} masked_during_commit"
4230 );
4231
4232 assert!(ctx.tick());
4234 ops.fetch_add(1, Ordering::Release);
4235 },
4236 move || {
4237 fin.store(true, Ordering::Release);
4238 },
4239 );
4240
4241 assert_eq!(
4242 ops_completed.load(Ordering::Acquire),
4243 3,
4244 "bead_id={BEAD_ID} all_ops_completed"
4245 );
4246 assert!(
4247 finalizer_ran.load(Ordering::Acquire),
4248 "bead_id={BEAD_ID} finalizer_ran"
4249 );
4250
4251 assert!(cx.checkpoint().is_err());
4253 }
4254
4255 #[test]
4256 fn test_commit_section_enforces_poll_quota() {
4257 let cx = Cx::<FullCaps>::new();
4259 cx.transition_to_running();
4260
4261 let ticks_succeeded = Arc::new(AtomicU32::new(0));
4262 let ts = Arc::clone(&ticks_succeeded);
4263
4264 cx.commit_section(
4265 3,
4266 |ctx| {
4267 assert_eq!(ctx.poll_remaining(), 3);
4268 for _ in 0..5 {
4269 if ctx.tick() {
4270 ts.fetch_add(1, Ordering::Release);
4271 }
4272 }
4273 },
4274 || {},
4275 );
4276
4277 assert_eq!(
4278 ticks_succeeded.load(Ordering::Acquire),
4279 3,
4280 "bead_id={BEAD_ID} poll_quota_enforced"
4281 );
4282 }
4283
4284 #[test]
4285 fn test_cancel_unaware_hot_loop_detected() {
4286 let cx = Cx::<FullCaps>::new();
4289 cx.transition_to_running();
4290
4291 let deadline = 100u32;
4294 let mut iterations_without_checkpoint = 0u32;
4295 let mut detected_unaware = false;
4296
4297 cx.cancel_with_reason(CancelReason::UserInterrupt);
4298
4299 for _i in 0..200u32 {
4300 iterations_without_checkpoint += 1;
4301 if iterations_without_checkpoint >= deadline {
4302 detected_unaware = true;
4303 break;
4304 }
4305 }
4307
4308 assert!(
4309 detected_unaware,
4310 "bead_id={BEAD_ID} cancel_unaware_loop_detected"
4311 );
4312
4313 let cx2 = Cx::<FullCaps>::new();
4315 cx2.transition_to_running();
4316 cx2.cancel_with_reason(CancelReason::UserInterrupt);
4317 let mut compliant_iters = 0u32;
4318 for _ in 0..200u32 {
4319 if cx2.checkpoint().is_err() {
4320 break;
4321 }
4322 compliant_iters += 1;
4323 }
4324 assert_eq!(
4325 compliant_iters, 0,
4326 "bead_id={BEAD_ID} compliant_loop_exits_immediately"
4327 );
4328 }
4329
4330 #[test]
4331 fn test_write_coordinator_commit_section() {
4332 let cx = Cx::<FullCaps>::new();
4335 cx.transition_to_running();
4336
4337 let proof_published = Arc::new(AtomicBool::new(false));
4338 let marker_published = Arc::new(AtomicBool::new(false));
4339 let reservation_released = Arc::new(AtomicBool::new(false));
4340
4341 let proof = Arc::clone(&proof_published);
4342 let marker = Arc::clone(&marker_published);
4343 let release = Arc::clone(&reservation_released);
4344
4345 cx.commit_section(
4346 10,
4347 |ctx| {
4348 assert!(ctx.tick());
4350
4351 cx.cancel_with_reason(CancelReason::RegionClose);
4353
4354 assert!(ctx.tick());
4356 proof.store(true, Ordering::Release);
4357 assert!(cx.checkpoint().is_ok());
4359
4360 assert!(ctx.tick());
4362 marker.store(true, Ordering::Release);
4363 },
4364 move || {
4365 release.store(true, Ordering::Release);
4367 },
4368 );
4369
4370 assert!(
4371 proof_published.load(Ordering::Acquire),
4372 "bead_id={BEAD_ID} proof_published"
4373 );
4374 assert!(
4375 marker_published.load(Ordering::Acquire),
4376 "bead_id={BEAD_ID} marker_published"
4377 );
4378 assert!(
4379 reservation_released.load(Ordering::Acquire),
4380 "bead_id={BEAD_ID} reservation_released"
4381 );
4382
4383 assert!(cx.checkpoint().is_err());
4385 }
4386
4387 #[test]
4392 fn test_trace_ids_default_to_zero() {
4393 let cx = Cx::<FullCaps>::new();
4394 assert_eq!(cx.trace_id(), 0);
4395 assert_eq!(cx.decision_id(), 0);
4396 assert_eq!(cx.policy_id(), 0);
4397 }
4398
4399 #[test]
4400 fn test_with_trace_context_sets_all_ids() {
4401 let cx = Cx::<FullCaps>::new().with_trace_context(42, 99, 7);
4402 assert_eq!(cx.trace_id(), 42);
4403 assert_eq!(cx.decision_id(), 99);
4404 assert_eq!(cx.policy_id(), 7);
4405 }
4406
4407 #[test]
4408 fn test_with_decision_id_preserves_other_ids() {
4409 let cx = Cx::<FullCaps>::new()
4410 .with_trace_context(10, 20, 30)
4411 .with_decision_id(55);
4412 assert_eq!(cx.trace_id(), 10);
4413 assert_eq!(cx.decision_id(), 55);
4414 assert_eq!(cx.policy_id(), 30);
4415 }
4416
4417 #[test]
4418 fn test_with_policy_id_preserves_other_ids() {
4419 let cx = Cx::<FullCaps>::new()
4420 .with_trace_context(100, 200, 300)
4421 .with_policy_id(88);
4422 assert_eq!(cx.trace_id(), 100);
4423 assert_eq!(cx.decision_id(), 200);
4424 assert_eq!(cx.policy_id(), 88);
4425 }
4426
4427 #[test]
4428 #[allow(clippy::redundant_clone)]
4429 fn test_clone_propagates_trace_ids() {
4430 let cx = Cx::<FullCaps>::new().with_trace_context(1, 2, 3);
4431 let cloned = cx.clone();
4432 assert_eq!(cloned.trace_id(), 1);
4433 assert_eq!(cloned.decision_id(), 2);
4434 assert_eq!(cloned.policy_id(), 3);
4435 }
4436
4437 #[test]
4438 fn test_restrict_propagates_trace_ids() {
4439 let cx = Cx::<FullCaps>::new();
4440 let compute = cx.restrict::<ComputeCaps>();
4441 assert_eq!(compute.trace_id(), 0);
4442 assert_eq!(compute.decision_id(), 0);
4443 assert_eq!(compute.policy_id(), 0);
4444 }
4445
4446 #[test]
4447 fn test_scope_with_budget_propagates_trace_ids() {
4448 let cx = Cx::<FullCaps>::new().with_trace_context(5, 6, 7);
4449 let scoped = cx.scope_with_budget(Budget::MINIMAL);
4450 assert_eq!(scoped.trace_id(), 5);
4451 assert_eq!(scoped.decision_id(), 6);
4452 assert_eq!(scoped.policy_id(), 7);
4453 assert_eq!(scoped.budget().poll_quota, Budget::MINIMAL.poll_quota);
4455 }
4456
4457 #[test]
4458 fn test_cleanup_scope_propagates_trace_ids() {
4459 let cx = Cx::<FullCaps>::new().with_trace_context(11, 22, 33);
4460 let cleanup = cx.cleanup_scope();
4461 assert_eq!(cleanup.trace_id(), 11);
4462 assert_eq!(cleanup.decision_id(), 22);
4463 assert_eq!(cleanup.policy_id(), 33);
4464 }
4465
4466 #[test]
4467 fn test_create_child_propagates_trace_ids() {
4468 let parent = Cx::<FullCaps>::new().with_trace_context(50, 60, 70);
4469 let child = parent.create_child();
4470 assert_eq!(child.trace_id(), 50);
4471 assert_eq!(child.decision_id(), 60);
4472 assert_eq!(child.policy_id(), 70);
4473 parent.cancel();
4475 assert!(parent.is_cancel_requested());
4476 assert!(child.is_cancel_requested()); }
4478
4479 #[test]
4480 fn test_trace_ids_independent_across_children() {
4481 let parent = Cx::<FullCaps>::new().with_trace_context(1, 2, 3);
4482 let child1 = parent.create_child().with_decision_id(100);
4483 let child2 = parent.create_child().with_decision_id(200);
4484 assert_eq!(child1.trace_id(), 1);
4486 assert_eq!(child2.trace_id(), 1);
4487 assert_eq!(child1.decision_id(), 100);
4488 assert_eq!(child2.decision_id(), 200);
4489 assert_eq!(parent.decision_id(), 2);
4491 }
4492
4493 #[test]
4494 fn test_with_budget_starts_at_zero_trace_ids() {
4495 let cx = Cx::<FullCaps>::with_budget(Budget::MINIMAL);
4496 assert_eq!(cx.trace_id(), 0);
4497 assert_eq!(cx.decision_id(), 0);
4498 assert_eq!(cx.policy_id(), 0);
4499 }
4500 #[test]
4503 fn blocking_io_inline_safe_defaults_false() {
4504 let cx = Cx::new();
4505 assert!(!cx.blocking_io_inline_safe());
4506 }
4507
4508 #[test]
4509 fn blocking_io_inline_safe_shared_through_clone() {
4510 let cx = Cx::new();
4511 let clone = cx.clone();
4512 cx.mark_blocking_io_inline_safe();
4513 assert!(clone.blocking_io_inline_safe());
4514 }
4515
4516 #[test]
4517 fn blocking_io_inline_safe_inherited_by_create_child() {
4518 let cx = Cx::new();
4519 cx.mark_blocking_io_inline_safe();
4520 let child = cx.create_child();
4521 assert!(child.blocking_io_inline_safe());
4522 }
4523
4524 #[test]
4525 fn blocking_io_inline_safe_not_invented_by_child_of_unset_parent() {
4526 let cx = Cx::new();
4527 let child = cx.create_child();
4528 assert!(!child.blocking_io_inline_safe());
4529 child.mark_blocking_io_inline_safe();
4532 assert!(!cx.blocking_io_inline_safe());
4533 }
4534}