1use std::future::Future;
10use std::pin::Pin;
11use std::sync::LazyLock;
12use std::task::{Context, Poll, ready};
13
14use tokio::runtime::{Builder, Runtime};
15use tokio::task::JoinHandle;
16
17mod bufpool;
18pub mod immortal;
19pub mod reaper;
20mod timeout;
21
22pub use bufpool::{pooled_read, pooled_read_callback};
23pub use immortal::{Immortal, RespawnStrategy};
24pub use reaper::TaskReaper;
25pub use timeout::TimeoutExt;
26
27static RUNTIME: LazyLock<Runtime> = LazyLock::new(|| {
33 Builder::new_multi_thread()
34 .enable_all()
35 .build()
36 .expect("could not build the global tokio runtime")
37});
38
39pub fn handle() -> tokio::runtime::Handle {
41 RUNTIME.handle().clone()
42}
43
44pub fn spawn<F>(future: F) -> Task<F::Output>
46where
47 F: Future + Send + 'static,
48 F::Output: Send + 'static,
49{
50 Task(Some(RUNTIME.spawn(future)))
51}
52
53pub fn spawn_blocking<F, R>(f: F) -> Task<R>
55where
56 F: FnOnce() -> R + Send + 'static,
57 R: Send + 'static,
58{
59 Task(Some(RUNTIME.spawn_blocking(f)))
60}
61
62pub fn block_on<F: Future>(future: F) -> F::Output {
68 RUNTIME.block_on(future)
69}
70
71#[must_use = "dropping a Task cancels the task; use .detach() to let it run in the background"]
78pub struct Task<T>(Option<JoinHandle<T>>);
79
80impl<T> Task<T> {
81 pub fn detach(mut self) {
84 self.0.take();
85 }
86
87 pub async fn cancel(mut self) {
92 if let Some(handle) = self.0.take() {
93 handle.abort();
94 let _ = handle.await;
95 }
96 }
97}
98
99impl<T> Drop for Task<T> {
100 fn drop(&mut self) {
101 if let Some(handle) = &self.0 {
102 handle.abort();
103 }
104 }
105}
106
107impl<T> Future for Task<T> {
108 type Output = T;
109 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
110 let handle = self
111 .0
112 .as_mut()
113 .expect("Task polled after being detached or cancelled");
114 match ready!(Pin::new(handle).poll(cx)) {
115 Ok(value) => Poll::Ready(value),
116 Err(err) if err.is_panic() => std::panic::resume_unwind(err.into_panic()),
117 Err(_) => Poll::Pending,
122 }
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use std::sync::Arc;
130 use std::sync::atomic::{AtomicBool, Ordering};
131 use std::time::Duration;
132
133 #[test]
134 fn drop_cancels_the_task() {
135 block_on(async {
136 let flag = Arc::new(AtomicBool::new(false));
137 let f2 = flag.clone();
138 let task = spawn(async move {
139 tokio::time::sleep(Duration::from_millis(100)).await;
140 f2.store(true, Ordering::SeqCst);
141 });
142 drop(task);
143 tokio::time::sleep(Duration::from_millis(300)).await;
144 assert!(
145 !flag.load(Ordering::SeqCst),
146 "a dropped Task must be cancelled"
147 );
148 });
149 }
150
151 #[test]
152 fn detach_runs_to_completion() {
153 block_on(async {
154 let flag = Arc::new(AtomicBool::new(false));
155 let f2 = flag.clone();
156 spawn(async move {
157 tokio::time::sleep(Duration::from_millis(50)).await;
158 f2.store(true, Ordering::SeqCst);
159 })
160 .detach();
161 tokio::time::sleep(Duration::from_millis(300)).await;
162 assert!(
163 flag.load(Ordering::SeqCst),
164 "a detached Task must run to completion"
165 );
166 });
167 }
168
169 #[test]
170 fn await_yields_output() {
171 block_on(async {
172 let task = spawn(async { 42u32 });
173 assert_eq!(task.await, 42);
174 });
175 }
176
177 #[test]
178 fn timeout_returns_none_on_elapse() {
179 block_on(async {
180 let r = tokio::time::sleep(Duration::from_secs(10))
181 .timeout(Duration::from_millis(50))
182 .await;
183 assert!(r.is_none());
184 });
185 }
186
187 #[test]
188 fn reaper_cancels_on_drop() {
189 block_on(async {
190 let flag = Arc::new(AtomicBool::new(false));
191 let f2 = flag.clone();
192 let reaper = TaskReaper::new();
193 reaper.attach(spawn(async move {
194 tokio::time::sleep(Duration::from_millis(100)).await;
195 f2.store(true, Ordering::SeqCst);
196 }));
197 tokio::time::sleep(Duration::from_millis(10)).await;
199 drop(reaper);
200 tokio::time::sleep(Duration::from_millis(300)).await;
201 assert!(
202 !flag.load(Ordering::SeqCst),
203 "dropping the reaper must cancel attached tasks"
204 );
205 });
206 }
207}