1use std::{
2 any::Any,
3 collections::HashMap,
4 error::Error,
5 fmt,
6 future::Future,
7 mem::transmute,
8 pin::Pin,
9 ptr::NonNull,
10 sync::atomic::{AtomicBool, Ordering},
11 task::{Context, Poll},
12};
13
14use tokio::{
15 runtime::{Handle, RuntimeFlavor},
16 task::{block_in_place, AbortHandle, Id, JoinSet},
17};
18
19type TokioJoinError = tokio::task::JoinError;
20
21#[derive(Default)]
41pub struct ScopedJoinSet<'scope, T>
42where
43 T: 'static,
44{
45 join_set: JoinSet<Option<T>>,
46 holders: HashMap<Id, FutureHolder<'scope, T>>,
47}
48
49impl<'scope, T> ScopedJoinSet<'scope, T>
50where
51 T: 'static,
52{
53 pub fn new() -> Self {
54 Self {
55 join_set: JoinSet::new(),
56 holders: HashMap::new(),
57 }
58 }
59
60 pub fn spawn<F>(&mut self, task: F)
61 where
62 F: Future<Output = T> + Send + 'scope,
63 T: Send,
64 {
65 let alive: NonNull<AtomicBool> = Box::leak(Box::new(AtomicBool::new(true))).into();
66 let future: NonNull<F> = Box::leak(Box::new(task)).into();
67 let weak_future = unsafe {
68 type Src<'scope, T> = NonNull<dyn Future<Output = T> + 'scope>;
69 type Dst<T> = NonNull<dyn Future<Output = T>>;
70 let future = transmute::<Src<'scope, T>, Dst<T>>(future);
71 WeakFuture { future, alive }
72 };
73 let handle = self.join_set.spawn(weak_future);
74 let holder = FutureHolder {
75 abort_handle: handle.clone(),
76 future,
77 alive,
78 };
79 self.holders.insert(handle.id(), holder);
80 }
81
82 pub fn is_empty(&self) -> bool {
83 self.join_set.is_empty()
84 }
85
86 pub async fn join_next(&mut self) -> Option<Result<T, JoinError>> {
87 match self.join_set.join_next_with_id().await? {
88 Ok((id, Some(value))) => {
89 self.holders.remove(&id);
90 Some(Ok(value))
91 }
92 Ok((id, None)) => {
93 self.holders.remove(&id);
94 Some(Err(JoinError::Cancelled))
95 }
96 Err(error) => {
97 self.holders.remove(&error.id());
98 Some(Err(error.into()))
99 }
100 }
101 }
102}
103
104unsafe impl<'scope, T> Send for ScopedJoinSet<'scope, T> {}
105
106struct FutureHolder<'scope, T>
107where
108 T: 'static,
109{
110 abort_handle: AbortHandle,
111 future: NonNull<dyn Future<Output = T> + Send + 'scope>,
112 alive: NonNull<AtomicBool>,
113}
114
115impl<'scope, T> Drop for FutureHolder<'scope, T> {
116 fn drop(&mut self) {
117 self.abort_handle.abort();
118
119 if Handle::current().runtime_flavor() == RuntimeFlavor::MultiThread {
120 let alive = unsafe { self.alive.as_ref() };
121 if alive.load(Ordering::Acquire) {
123 block_in_place(|| {
124 while alive.load(Ordering::Acquire) {
125 std::hint::spin_loop();
126 }
127 });
128 }
129 drop(unsafe { Box::from_raw(self.future.as_ptr()) });
130 drop(unsafe { Box::from_raw(self.alive.as_mut()) });
131 } else {
132 drop(unsafe { Box::from_raw(self.future.as_ptr()) });
133 }
134 }
135}
136
137struct WeakFuture<T> {
138 future: NonNull<dyn Future<Output = T>>,
139 alive: NonNull<AtomicBool>,
140}
141
142impl<T> Drop for WeakFuture<T> {
143 fn drop(&mut self) {
144 if Handle::current().runtime_flavor() == RuntimeFlavor::MultiThread {
146 unsafe { self.alive.as_ref() }.store(false, Ordering::Release);
147 } else {
148 drop(unsafe { Box::from_raw(self.alive.as_mut()) });
149 }
150 }
151}
152
153unsafe impl<T: Send> Send for WeakFuture<T> {}
154
155impl<T> Future for WeakFuture<T> {
156 type Output = Option<T>;
157
158 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
159 let future_ref = unsafe { self.future.as_mut() };
160 let future_pin = unsafe { Pin::new_unchecked(future_ref) };
161 Future::poll(future_pin, cx).map(Some)
162 }
163}
164
165#[derive(Debug)]
166pub enum JoinError {
167 Cancelled,
168 Panicked(Box<dyn Any + Send + 'static>),
169}
170
171impl fmt::Display for JoinError {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 match self {
174 JoinError::Cancelled => write!(f, "task was cancelled"),
175 JoinError::Panicked(_) => write!(f, "task panicked"),
176 }
177 }
178}
179
180impl Error for JoinError {}
181
182impl JoinError {
183 pub fn is_cancelled(&self) -> bool {
184 matches!(self, JoinError::Cancelled)
185 }
186
187 pub fn is_panic(&self) -> bool {
188 matches!(self, JoinError::Panicked(_))
189 }
190}
191
192impl From<TokioJoinError> for JoinError {
193 fn from(err: TokioJoinError) -> Self {
194 if err.is_cancelled() {
195 JoinError::Cancelled
196 } else if err.is_panic() {
197 JoinError::Panicked(err.into_panic())
198 } else {
199 JoinError::Cancelled
200 }
201 }
202}