1use std::future::Future;
44use std::pin::Pin;
45use std::sync::Arc;
46use std::task::{Context, Poll};
47
48use futures::FutureExt;
49use tokio::runtime::{Handle, Runtime};
50use tokio::task::{self, JoinHandle as TokioJoinHandle};
51
52#[derive(Debug)]
54pub struct AbortOnDropHandle<T>(JoinHandle<T>);
55
56impl<T> AbortOnDropHandle<T> {
57 pub fn is_finished(&self) -> bool {
59 self.0.inner.is_finished()
60 }
61
62 pub async fn abort_and_wait(mut self) {
64 self.0.inner.abort();
65 let _ = (&mut self.0.inner).await;
66 }
67
68 }
70
71impl<T> Drop for AbortOnDropHandle<T> {
72 fn drop(&mut self) {
73 self.0.inner.abort();
74 }
75}
76
77impl<T> Future for AbortOnDropHandle<T> {
78 type Output = T;
79
80 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
81 self.0.poll_unpin(cx)
82 }
83}
84
85#[derive(Debug)]
95pub struct JoinHandle<T> {
96 inner: TokioJoinHandle<T>,
97 runtime_shutting_down: bool,
98}
99
100impl<T> JoinHandle<T> {
101 fn new(handle: TokioJoinHandle<T>) -> Self {
104 Self {
105 inner: handle,
106 runtime_shutting_down: false,
107 }
108 }
109}
110
111impl<T> Future for JoinHandle<T> {
112 type Output = T;
113
114 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
115 if self.runtime_shutting_down {
116 return Poll::Pending;
117 }
118 match self.inner.poll_unpin(cx) {
119 Poll::Ready(Ok(res)) => Poll::Ready(res),
120 Poll::Ready(Err(err)) => {
121 match err.try_into_panic() {
122 Ok(panic) => std::panic::resume_unwind(panic),
123 Err(err) => {
124 assert!(
125 err.is_cancelled(),
126 "join errors are either cancellations or panics"
127 );
128 self.runtime_shutting_down = true;
136 Poll::Pending
137 }
138 }
139 }
140 Poll::Pending => Poll::Pending,
141 }
142 }
143}
144
145impl<T> JoinHandle<T> {
146 pub fn abort_on_drop(self) -> AbortOnDropHandle<T> {
148 AbortOnDropHandle(self)
149 }
150
151 pub fn is_finished(&self) -> bool {
153 self.inner.is_finished()
154 }
155
156 pub async fn abort_and_wait(self) {
158 self.inner.abort();
159 let _ = self.inner.await;
160 }
161
162 pub fn into_tokio_handle(self) -> TokioJoinHandle<T> {
164 self.inner
165 }
166
167 }
169
170#[cfg(not(tokio_unstable))]
175#[track_caller]
176pub fn spawn<Fut, Name, NameClosure>(_nc: NameClosure, future: Fut) -> JoinHandle<Fut::Output>
177where
178 Name: AsRef<str>,
179 NameClosure: FnOnce() -> Name,
180 Fut: Future + Send + 'static,
181 Fut::Output: Send + 'static,
182{
183 let future: Pin<Box<dyn Future<Output = Fut::Output> + Send>> = Box::pin(future);
187 #[allow(clippy::disallowed_methods)]
188 JoinHandle::new(tokio::spawn(future))
189}
190
191#[cfg(tokio_unstable)]
196#[track_caller]
197pub fn spawn<Fut, Name, NameClosure>(nc: NameClosure, future: Fut) -> JoinHandle<Fut::Output>
198where
199 Name: AsRef<str>,
200 NameClosure: FnOnce() -> Name,
201 Fut: Future + Send + 'static,
202 Fut::Output: Send + 'static,
203{
204 let future: Pin<Box<dyn Future<Output = Fut::Output> + Send>> = Box::pin(future);
208 #[allow(clippy::disallowed_methods)]
209 JoinHandle::new(
210 task::Builder::new()
211 .name(&format!("{}:{}", Handle::current().id(), nc().as_ref()))
212 .spawn(future)
213 .expect("task spawning cannot fail"),
214 )
215}
216
217#[cfg(not(tokio_unstable))]
223#[track_caller]
224#[allow(clippy::disallowed_methods)]
225pub fn spawn_blocking<Function, Output, Name, NameClosure>(
226 _nc: NameClosure,
227 function: Function,
228) -> JoinHandle<Output>
229where
230 Name: AsRef<str>,
231 NameClosure: FnOnce() -> Name,
232 Function: FnOnce() -> Output + Send + 'static,
233 Output: Send + 'static,
234{
235 JoinHandle::new(task::spawn_blocking(function))
236}
237
238#[cfg(tokio_unstable)]
244#[track_caller]
245#[allow(clippy::disallowed_methods)]
246pub fn spawn_blocking<Function, Output, Name, NameClosure>(
247 nc: NameClosure,
248 function: Function,
249) -> JoinHandle<Output>
250where
251 Name: AsRef<str>,
252 NameClosure: FnOnce() -> Name,
253 Function: FnOnce() -> Output + Send + 'static,
254 Output: Send + 'static,
255{
256 JoinHandle::new(
257 task::Builder::new()
258 .name(&format!("{}:{}", Handle::current().id(), nc().as_ref()))
259 .spawn_blocking(function)
260 .expect("task spawning cannot fail"),
261 )
262}
263
264pub trait RuntimeExt {
268 #[track_caller]
274 fn spawn_blocking_named<Function, Output, Name, NameClosure>(
275 &self,
276 nc: NameClosure,
277 function: Function,
278 ) -> JoinHandle<Output>
279 where
280 Name: AsRef<str>,
281 NameClosure: FnOnce() -> Name,
282 Function: FnOnce() -> Output + Send + 'static,
283 Output: Send + 'static;
284
285 #[track_caller]
290 fn spawn_named<Fut, Name, NameClosure>(
291 &self,
292 _nc: NameClosure,
293 future: Fut,
294 ) -> JoinHandle<Fut::Output>
295 where
296 Name: AsRef<str>,
297 NameClosure: FnOnce() -> Name,
298 Fut: Future + Send + 'static,
299 Fut::Output: Send + 'static;
300}
301
302impl RuntimeExt for &Runtime {
303 fn spawn_blocking_named<Function, Output, Name, NameClosure>(
304 &self,
305 nc: NameClosure,
306 function: Function,
307 ) -> JoinHandle<Output>
308 where
309 Name: AsRef<str>,
310 NameClosure: FnOnce() -> Name,
311 Function: FnOnce() -> Output + Send + 'static,
312 Output: Send + 'static,
313 {
314 let _g = self.enter();
315 spawn_blocking(nc, function)
316 }
317
318 fn spawn_named<Fut, Name, NameClosure>(
319 &self,
320 nc: NameClosure,
321 future: Fut,
322 ) -> JoinHandle<Fut::Output>
323 where
324 Name: AsRef<str>,
325 NameClosure: FnOnce() -> Name,
326 Fut: Future + Send + 'static,
327 Fut::Output: Send + 'static,
328 {
329 let _g = self.enter();
330 spawn(nc, future)
331 }
332}
333
334impl RuntimeExt for Arc<Runtime> {
335 fn spawn_blocking_named<Function, Output, Name, NameClosure>(
336 &self,
337 nc: NameClosure,
338 function: Function,
339 ) -> JoinHandle<Output>
340 where
341 Name: AsRef<str>,
342 NameClosure: FnOnce() -> Name,
343 Function: FnOnce() -> Output + Send + 'static,
344 Output: Send + 'static,
345 {
346 (&**self).spawn_blocking_named(nc, function)
347 }
348
349 fn spawn_named<Fut, Name, NameClosure>(
350 &self,
351 nc: NameClosure,
352 future: Fut,
353 ) -> JoinHandle<Fut::Output>
354 where
355 Name: AsRef<str>,
356 NameClosure: FnOnce() -> Name,
357 Fut: Future + Send + 'static,
358 Fut::Output: Send + 'static,
359 {
360 (&**self).spawn_named(nc, future)
361 }
362}
363
364impl RuntimeExt for Handle {
365 fn spawn_blocking_named<Function, Output, Name, NameClosure>(
366 &self,
367 nc: NameClosure,
368 function: Function,
369 ) -> JoinHandle<Output>
370 where
371 Name: AsRef<str>,
372 NameClosure: FnOnce() -> Name,
373 Function: FnOnce() -> Output + Send + 'static,
374 Output: Send + 'static,
375 {
376 let _g = self.enter();
377 spawn_blocking(nc, function)
378 }
379
380 fn spawn_named<Fut, Name, NameClosure>(
381 &self,
382 nc: NameClosure,
383 future: Fut,
384 ) -> JoinHandle<Fut::Output>
385 where
386 Name: AsRef<str>,
387 NameClosure: FnOnce() -> Name,
388 Fut: Future + Send + 'static,
389 Fut::Output: Send + 'static,
390 {
391 let _g = self.enter();
392 spawn(nc, future)
393 }
394}
395
396pub trait JoinSetExt<T> {
400 #[track_caller]
405 fn spawn_named<Fut, Name, NameClosure>(
406 &mut self,
407 nc: NameClosure,
408 future: Fut,
409 ) -> tokio::task::AbortHandle
410 where
411 Name: AsRef<str>,
412 NameClosure: FnOnce() -> Name,
413 Fut: Future<Output = T> + Send + 'static,
414 T: Send + 'static;
415}
416
417impl<T> JoinSetExt<T> for tokio::task::JoinSet<T> {
418 #[allow(unused_variables)]
420 fn spawn_named<Fut, Name, NameClosure>(
421 &mut self,
422 nc: NameClosure,
423 future: Fut,
424 ) -> tokio::task::AbortHandle
425 where
426 Name: AsRef<str>,
427 NameClosure: FnOnce() -> Name,
428 Fut: Future<Output = T> + Send + 'static,
429 T: Send + 'static,
430 {
431 let future: Pin<Box<dyn Future<Output = T> + Send>> = Box::pin(future);
436 #[cfg(tokio_unstable)]
437 #[allow(clippy::disallowed_methods)]
438 {
439 self.build_task()
440 .name(&format!("{}:{}", Handle::current().id(), nc().as_ref()))
441 .spawn(future)
442 .expect("task spawning cannot fail")
443 }
444 #[cfg(not(tokio_unstable))]
445 #[allow(clippy::disallowed_methods)]
446 {
447 self.spawn(future)
448 }
449 }
450}
451
452#[cfg(test)]
453mod tests {
454 use std::future;
455 use std::sync::Arc;
456 use std::sync::atomic::{AtomicBool, Ordering};
457
458 use tokio::sync::oneshot;
459
460 struct SetOnDrop(Arc<AtomicBool>);
461
462 impl Drop for SetOnDrop {
463 fn drop(&mut self) {
464 self.0.store(true, Ordering::SeqCst);
465 }
466 }
467
468 #[mz_ore::test(tokio::test)]
469 async fn abort_on_drop_handle_waits_for_task_drop() {
470 let dropped = Arc::new(AtomicBool::new(false));
471 let guard = SetOnDrop(Arc::clone(&dropped));
472 let (started_tx, started_rx) = oneshot::channel();
473 let handle = super::spawn(|| "abort_on_drop_handle_waits_for_task_drop", async move {
474 let _guard = guard;
475 started_tx.send(()).expect("receiver remains live");
476 future::pending::<()>().await;
477 })
478 .abort_on_drop();
479
480 started_rx.await.expect("task starts");
481 handle.abort_and_wait().await;
482
483 assert!(dropped.load(Ordering::SeqCst));
484 }
485}