sequoia_async/
view_ext.rs1#![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}