Skip to main content

sequoia_async/
view_ext.rs

1#![forbid(unsafe_code)]
2
3use crate::task::{TaskHandle, TaskId};
4
5pub struct ScopedTask {
6    handle: Option<TaskHandle>,
7    cancelled: bool,
8}
9
10impl ScopedTask {
11    pub fn from_handle(handle: TaskHandle) -> Self {
12        Self {
13            handle: Some(handle),
14            cancelled: false,
15        }
16    }
17
18    pub fn new(id: TaskId, cancel: impl Fn(TaskId) -> bool + 'static) -> Self {
19        Self {
20            handle: Some(TaskHandle::new(id, cancel)),
21            cancelled: false,
22        }
23    }
24
25    pub fn id(&self) -> Option<TaskId> {
26        self.handle.as_ref().map(|h| h.id())
27    }
28
29    pub fn cancel(&mut self) -> bool {
30        if self.cancelled {
31            return false;
32        }
33        self.cancelled = true;
34        match &self.handle {
35            Some(h) => h.cancel(),
36            None => false,
37        }
38    }
39
40    pub fn detach(mut self) -> Option<TaskId> {
41        let id = self.id();
42        self.cancelled = true;
43        self.handle.take();
44
45        id
46    }
47
48    pub fn is_active(&self) -> bool {
49        !self.cancelled && self.handle.is_some()
50    }
51}
52
53impl Drop for ScopedTask {
54    fn drop(&mut self) {
55        if !self.cancelled
56            && let Some(h) = &self.handle
57        {
58            h.cancel();
59        }
60    }
61}
62
63impl std::fmt::Debug for ScopedTask {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        f.debug_struct("ScopedTask")
66            .field("id", &self.handle.as_ref().map(|h| h.id().get()))
67            .field("cancelled", &self.cancelled)
68            .finish()
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use std::cell::Cell;
76    use std::rc::Rc;
77
78    #[test]
79    fn drop_cancels_once() {
80        let n = Rc::new(Cell::new(0));
81        let probe = Rc::clone(&n);
82        {
83            let _guard = ScopedTask::new(crate::task::TaskId::new(1), move |_| {
84                probe.set(probe.get() + 1);
85                true
86            });
87        }
88        assert_eq!(n.get(), 1);
89    }
90
91    #[test]
92    fn detach_skips_cancel() {
93        let n = Rc::new(Cell::new(0));
94        let probe = Rc::clone(&n);
95        {
96            let guard = ScopedTask::new(crate::task::TaskId::new(2), move |_| {
97                probe.set(probe.get() + 1);
98                true
99            });
100            let _ = guard.detach();
101        }
102        assert_eq!(n.get(), 0);
103    }
104
105    #[test]
106    fn explicit_cancel_is_idempotent() {
107        let mut guard = ScopedTask::new(crate::task::TaskId::new(3), |_| true);
108        assert!(guard.cancel());
109        assert!(!guard.cancel());
110        assert!(!guard.is_active());
111    }
112
113    #[test]
114    fn from_handle_cancels_on_drop() {
115        let n = Rc::new(Cell::new(0));
116        let probe = Rc::clone(&n);
117        let handle = TaskHandle::new(crate::task::TaskId::new(4), move |_| {
118            probe.set(probe.get() + 1);
119            true
120        });
121        {
122            let _guard = ScopedTask::from_handle(handle);
123        }
124        assert_eq!(n.get(), 1);
125    }
126}