tessera_shard/async_support/
task_handles.rs1use std::future::Future;
8
9use futures_util::future::{AbortHandle, Abortable};
10use parking_lot::Mutex;
11
12#[cfg(not(target_family = "wasm"))]
13use tokio::task::JoinHandle;
14
15#[cfg(target_family = "wasm")]
16use wasm_bindgen_futures::spawn_local;
17
18#[cfg(not(target_family = "wasm"))]
19pub type TaskRuntimeHandle = JoinHandle<()>;
21#[cfg(target_family = "wasm")]
22pub type TaskRuntimeHandle = ();
24
25pub struct TaskHandle {
27 pub handle: TaskRuntimeHandle,
29 cancel: AbortHandle,
30}
31
32pub struct TaskHandles {
34 tasks: Mutex<Vec<TaskHandle>>,
35 #[cfg(not(target_family = "wasm"))]
36 executor: tokio::runtime::Handle,
37}
38
39impl Default for TaskHandles {
40 fn default() -> Self {
41 Self::new()
42 }
43}
44
45impl Drop for TaskHandles {
46 fn drop(&mut self) {
47 self.cancel_all();
48 }
49}
50
51impl TaskHandles {
52 pub fn new() -> Self {
54 Self {
55 tasks: Mutex::new(Vec::new()),
56 #[cfg(not(target_family = "wasm"))]
57 executor:
58 tessera_ui::__private::runtime_resource::<super::tokio_runtime::TaskExecutor>()
59 .handle()
60 .clone(),
61 }
62 }
63
64 pub fn spawn<F>(&self, fut: F)
66 where
67 F: Future<Output = ()> + Send + 'static,
68 {
69 #[cfg(not(target_family = "wasm"))]
70 let (handle, cancel) = spawn_task(&self.executor, fut);
71 #[cfg(target_family = "wasm")]
72 let (handle, cancel) = spawn_task(fut);
73 self.tasks.lock().push(TaskHandle { handle, cancel });
74 }
75
76 pub fn cancel_all(&self) {
78 let mut tasks = self.tasks.lock();
79 for task in tasks.drain(..) {
80 task.cancel.abort();
81 }
82 }
83}
84
85#[cfg(not(target_family = "wasm"))]
86fn spawn_task<F>(executor: &tokio::runtime::Handle, fut: F) -> (TaskRuntimeHandle, AbortHandle)
87where
88 F: Future<Output = ()> + Send + 'static,
89{
90 let (cancel, registration) = AbortHandle::new_pair();
91 let wrapped = Abortable::new(fut, registration);
92 let handle = executor.spawn(async move {
93 let _ = wrapped.await;
94 });
95 (handle, cancel)
96}
97
98#[cfg(target_family = "wasm")]
99fn spawn_task<F>(fut: F) -> (TaskRuntimeHandle, AbortHandle)
100where
101 F: Future<Output = ()> + Send + 'static,
102{
103 let (cancel, registration) = AbortHandle::new_pair();
104 let wrapped = Abortable::new(fut, registration);
105 spawn_local(async move {
106 let _ = wrapped.await;
107 });
108 ((), cancel)
109}
110
111#[cfg(all(test, not(target_family = "wasm")))]
112mod tests {
113 use super::TaskHandles;
114
115 #[test]
116 fn session_teardown_cancels_jobs_even_when_the_task_collection_survives() {
117 let session = tessera_ui::RuntimeSession::new();
118 let tasks = session.run(TaskHandles::new);
119 tasks.spawn(std::future::pending());
120 let task = tasks.tasks.lock().pop().expect("registered job");
121 drop(session);
122 let executor = tokio::runtime::Builder::new_current_thread()
123 .build()
124 .expect("test executor");
125 let result = executor.block_on(task.handle);
126 assert!(
127 result
128 .expect_err("session must cancel its job")
129 .is_cancelled()
130 );
131 }
132}