1use crate::{MongrelError, Result};
4use std::sync::atomic::{AtomicU64, AtomicU8, Ordering};
5use std::sync::Arc;
6use std::time::{Duration, Instant};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[repr(u8)]
10pub enum CancellationReason {
11 None = 0,
12 ClientRequest = 1,
13 Deadline = 2,
14 ClientDisconnected = 3,
15 SessionClosed = 4,
16 ServerShutdown = 5,
17}
18
19impl CancellationReason {
20 pub const fn as_str(self) -> &'static str {
22 match self {
23 Self::None => "none",
24 Self::ClientRequest => "client_request",
25 Self::Deadline => "deadline",
26 Self::ClientDisconnected => "client_disconnected",
27 Self::SessionClosed => "session_closed",
28 Self::ServerShutdown => "server_shutdown",
29 }
30 }
31
32 pub fn from_protocol_str(value: &str) -> Option<Self> {
33 match value {
34 "none" => Some(Self::None),
35 "client_request" => Some(Self::ClientRequest),
36 "deadline" => Some(Self::Deadline),
37 "client_disconnected" => Some(Self::ClientDisconnected),
38 "session_closed" => Some(Self::SessionClosed),
39 "server_shutdown" => Some(Self::ServerShutdown),
40 _ => None,
41 }
42 }
43
44 fn from_u8(value: u8) -> Self {
45 match value {
46 1 => Self::ClientRequest,
47 2 => Self::Deadline,
48 3 => Self::ClientDisconnected,
49 4 => Self::SessionClosed,
50 5 => Self::ServerShutdown,
51 _ => Self::None,
52 }
53 }
54}
55
56#[derive(Debug)]
57struct CancellationState {
58 sequence: AtomicU64,
59 reason: AtomicU8,
60}
61
62#[derive(Debug, Default)]
63struct CancellationOrder {
64 next_sequence: u64,
65}
66
67#[derive(Debug, Clone)]
74pub struct ExecutionControl {
75 states: Arc<Vec<Arc<CancellationState>>>,
76 own: Arc<CancellationState>,
77 order: Arc<parking_lot::Mutex<CancellationOrder>>,
78 wake: Arc<tokio::sync::Notify>,
79 deadline: Option<Instant>,
80}
81
82impl ExecutionControl {
83 pub fn new(deadline: Option<Instant>) -> Self {
84 let own = Arc::new(CancellationState {
85 sequence: AtomicU64::new(0),
86 reason: AtomicU8::new(CancellationReason::None as u8),
87 });
88 Self {
89 states: Arc::new(vec![Arc::clone(&own)]),
90 own,
91 order: Arc::new(parking_lot::Mutex::new(CancellationOrder::default())),
92 wake: Arc::new(tokio::sync::Notify::new()),
93 deadline,
94 }
95 }
96
97 pub fn with_timeout(timeout: Duration) -> Self {
98 let now = Instant::now();
99 Self::new(Some(now.checked_add(timeout).unwrap_or(now)))
100 }
101
102 pub fn child_with_deadline(&self, deadline: Option<Instant>) -> Self {
103 let deadline = match (self.deadline, deadline) {
104 (Some(parent), Some(child)) => Some(parent.min(child)),
105 (Some(parent), None) => Some(parent),
106 (None, child) => child,
107 };
108 let own = Arc::new(CancellationState {
109 sequence: AtomicU64::new(0),
110 reason: AtomicU8::new(CancellationReason::None as u8),
111 });
112 let mut states = self.states.as_ref().clone();
113 states.push(Arc::clone(&own));
114 Self {
115 states: Arc::new(states),
116 own,
117 order: Arc::clone(&self.order),
118 wake: Arc::clone(&self.wake),
119 deadline,
120 }
121 }
122
123 pub fn child_with_timeout(&self, timeout: Duration) -> Self {
124 let now = Instant::now();
125 self.child_with_deadline(Some(now.checked_add(timeout).unwrap_or(now)))
126 }
127
128 pub fn cancel(&self, reason: CancellationReason) {
129 if reason == CancellationReason::None {
130 return;
131 }
132 let mut order = self.order.lock();
133 if self.own.reason.load(Ordering::Relaxed) == CancellationReason::None as u8 {
134 self.own
135 .sequence
136 .store(order.next_sequence, Ordering::Relaxed);
137 order.next_sequence = order.next_sequence.saturating_add(1);
138 self.own.reason.store(reason as u8, Ordering::Release);
139 }
140 drop(order);
141 self.wake.notify_waiters();
142 }
143
144 pub fn checkpoint(&self) -> Result<()> {
145 if self
146 .deadline
147 .is_some_and(|deadline| Instant::now() >= deadline)
148 {
149 self.cancel(CancellationReason::Deadline);
150 }
151 match self.reason() {
152 CancellationReason::None => Ok(()),
153 CancellationReason::Deadline => Err(MongrelError::DeadlineExceeded),
154 _ => Err(MongrelError::Cancelled),
155 }
156 }
157
158 pub async fn cancelled(&self) {
159 loop {
160 if self.checkpoint().is_err() {
161 return;
162 }
163 let notified = self.wake.notified();
164 if self.checkpoint().is_err() {
165 return;
166 }
167 if let Some(remaining) = self.remaining_duration() {
168 if tokio::time::timeout(remaining, notified).await.is_err() {
169 self.cancel(CancellationReason::Deadline);
170 return;
171 }
172 } else {
173 notified.await;
174 }
175 }
176 }
177
178 pub fn remaining_duration(&self) -> Option<Duration> {
179 self.deadline
180 .map(|deadline| deadline.saturating_duration_since(Instant::now()))
181 }
182
183 pub fn deadline(&self) -> Option<Instant> {
184 self.deadline
185 }
186
187 pub fn reason(&self) -> CancellationReason {
188 self.states
189 .iter()
190 .filter_map(|state| {
191 let reason = CancellationReason::from_u8(state.reason.load(Ordering::Acquire));
192 (reason != CancellationReason::None)
193 .then(|| (state.sequence.load(Ordering::Relaxed), reason))
194 })
195 .min_by_key(|(sequence, _)| *sequence)
196 .map_or(CancellationReason::None, |(_, reason)| reason)
197 }
198
199 pub fn is_cancelled(&self) -> bool {
200 if self
201 .deadline
202 .is_some_and(|deadline| Instant::now() >= deadline)
203 {
204 self.cancel(CancellationReason::Deadline);
205 }
206 self.reason() != CancellationReason::None
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn first_reason_wins() {
216 let control = ExecutionControl::new(None);
217 control.cancel(CancellationReason::ClientRequest);
218 control.cancel(CancellationReason::ServerShutdown);
219 assert_eq!(control.reason(), CancellationReason::ClientRequest);
220 assert!(matches!(control.checkpoint(), Err(MongrelError::Cancelled)));
221 }
222
223 #[test]
224 fn deadline_returns_structured_error() {
225 let control = ExecutionControl::new(Some(Instant::now()));
226 assert!(matches!(
227 control.checkpoint(),
228 Err(MongrelError::DeadlineExceeded)
229 ));
230 assert_eq!(control.reason(), CancellationReason::Deadline);
231 }
232
233 #[test]
234 fn children_inherit_parent_but_do_not_cancel_it() {
235 let parent = ExecutionControl::new(None);
236 let child = parent.child_with_deadline(None);
237 child.cancel(CancellationReason::ClientRequest);
238 assert!(parent.checkpoint().is_ok());
239 assert!(child.checkpoint().is_err());
240
241 let sibling = parent.child_with_deadline(None);
242 parent.cancel(CancellationReason::SessionClosed);
243 assert_eq!(sibling.reason(), CancellationReason::SessionClosed);
244 assert!(sibling.checkpoint().is_err());
245 }
246
247 #[test]
248 fn child_reason_uses_first_event_without_prior_observation() {
249 let parent = ExecutionControl::new(None);
250 let child = parent.child_with_deadline(None);
251
252 child.cancel(CancellationReason::ClientRequest);
253 parent.cancel(CancellationReason::ServerShutdown);
254
255 assert_eq!(child.reason(), CancellationReason::ClientRequest);
256 assert_eq!(parent.reason(), CancellationReason::ServerShutdown);
257 }
258
259 #[test]
260 fn child_reason_uses_first_parent_event_without_prior_observation() {
261 let parent = ExecutionControl::new(None);
262 let child = parent.child_with_deadline(None);
263
264 parent.cancel(CancellationReason::SessionClosed);
265 child.cancel(CancellationReason::ClientRequest);
266
267 assert_eq!(child.reason(), CancellationReason::SessionClosed);
268 }
269
270 #[test]
271 fn cross_thread_parent_child_order_is_stable() {
272 let parent = ExecutionControl::new(None);
273 let child = parent.child_with_deadline(None);
274 let (cancelled_tx, cancelled_rx) = std::sync::mpsc::sync_channel(0);
275 let child_task = {
276 let child = child.clone();
277 std::thread::spawn(move || {
278 child.cancel(CancellationReason::ClientRequest);
279 cancelled_tx.send(()).unwrap();
280 })
281 };
282
283 cancelled_rx.recv().unwrap();
284 parent.cancel(CancellationReason::ServerShutdown);
285 child_task.join().unwrap();
286
287 assert_eq!(child.reason(), CancellationReason::ClientRequest);
288 }
289
290 #[test]
291 fn concurrent_parent_child_cancellation_stays_stable() {
292 let parent = ExecutionControl::new(None);
293 let child = parent.child_with_deadline(None);
294 let barrier = Arc::new(std::sync::Barrier::new(3));
295 let parent_task = {
296 let parent = parent.clone();
297 let barrier = Arc::clone(&barrier);
298 std::thread::spawn(move || {
299 barrier.wait();
300 parent.cancel(CancellationReason::ServerShutdown);
301 })
302 };
303 let child_task = {
304 let child = child.clone();
305 let barrier = Arc::clone(&barrier);
306 std::thread::spawn(move || {
307 barrier.wait();
308 child.cancel(CancellationReason::ClientRequest);
309 })
310 };
311 barrier.wait();
312 parent_task.join().unwrap();
313 child_task.join().unwrap();
314
315 let observed = child.reason();
316 assert_ne!(observed, CancellationReason::None);
317 parent.cancel(CancellationReason::SessionClosed);
318 child.cancel(CancellationReason::ClientDisconnected);
319 assert_eq!(child.reason(), observed);
320 }
321
322 #[test]
323 fn child_deadline_cannot_weaken_parent() {
324 let parent_deadline = Instant::now() + Duration::from_secs(1);
325 let parent = ExecutionControl::new(Some(parent_deadline));
326 let child = parent.child_with_timeout(Duration::from_secs(60));
327 assert_eq!(child.deadline(), Some(parent_deadline));
328 }
329
330 #[test]
331 fn overflowing_timeouts_fail_closed_without_panicking() {
332 let control = ExecutionControl::with_timeout(Duration::MAX);
333 assert!(matches!(
334 control.checkpoint(),
335 Err(MongrelError::DeadlineExceeded)
336 ));
337
338 let parent = ExecutionControl::new(None);
339 let child = parent.child_with_timeout(Duration::MAX);
340 assert!(matches!(
341 child.checkpoint(),
342 Err(MongrelError::DeadlineExceeded)
343 ));
344 }
345}