Skip to main content

nuclei/
join_set.rs

1//! [`JoinSet`]: a set of tasks whose outputs come back in the order the tasks
2//! finish.
3//!
4//! Each slot of the set has a waker of its own, made once with the slot and
5//! kept for every member it holds: woken, it queues itself (at most once,
6//! its `queued` flag) in the set's ready queue, linked through the waker
7//! itself (the shard inbox's queue, proofs/tla/inbox), and wakes the set's
8//! awaiter. `join_next` polls only members that were woken, never the whole
9//! set, and a spawn into a set that has a free slot allocates nothing for
10//! the set. A slot reused by a later member may get a stale wake from the
11//! member before it, which costs one spurious poll and nothing else. A
12//! waker holds the set weakly: one that wakes after the set is gone does
13//! nothing, and nothing queued keeps the set alive.
14
15use core::future::Future;
16use core::pin::Pin;
17use core::ptr::NonNull;
18use core::task::{Context, Poll, Waker};
19use std::fmt;
20use std::sync::atomic::{AtomicBool, Ordering};
21use std::sync::{Arc, Weak};
22use std::task::Wake;
23
24use crate::rt::inbox::{Inbox, Link, Linked, Pop};
25use crate::sync::atomic_waker::AtomicWaker;
26use crate::task::{AbortHandle, Id, Join, JoinError, Task};
27
28/// What the slots' wakers share with the set.
29struct Shared {
30    /// Wakers of woken members, in wake order.
31    ready: Inbox<Queued>,
32    /// The task awaiting `join_next`.
33    awaiter: AtomicWaker,
34}
35
36/// A slot's waker: queues itself once per wake.
37struct SlotWake {
38    link: Link,
39    shared: Weak<Shared>,
40    slot: usize,
41    queued: AtomicBool,
42}
43
44impl Wake for SlotWake {
45    fn wake(self: Arc<Self>) {
46        self.wake_by_ref();
47    }
48
49    fn wake_by_ref(self: &Arc<Self>) {
50        if !self.queued.swap(true, Ordering::AcqRel)
51            && let Some(shared) = self.shared.upgrade()
52        {
53            shared.ready.push(Queued(Arc::clone(self)));
54            shared.awaiter.wake();
55        }
56    }
57}
58
59/// A slot's waker in the ready queue, with the reference that keeps it.
60struct Queued(Arc<SlotWake>);
61
62// SAFETY: the link is the waker's own field, kept alive by the reference
63// the item holds; a waker is `Send` and `Sync`.
64unsafe impl Linked for Queued {
65    fn into_link(self) -> NonNull<Link> {
66        let wake = Arc::into_raw(self.0);
67        // SAFETY: `wake` points at a live waker, whose link is in it.
68        unsafe { NonNull::new_unchecked(core::ptr::addr_of!((*wake).link).cast_mut()) }
69    }
70
71    unsafe fn from_link(link: NonNull<Link>) -> Self {
72        let offset = core::mem::offset_of!(SlotWake, link);
73        // SAFETY: the link is a waker's `link` field, given up by
74        // `into_link` with its reference.
75        Self(unsafe { Arc::from_raw(link.as_ptr().cast::<u8>().sub(offset).cast::<SlotWake>()) })
76    }
77}
78
79/// A place in the set: its waker, kept, and the member it holds.
80struct Slot<T> {
81    wake: Arc<SlotWake>,
82    waker: Waker,
83    member: Option<Member<T>>,
84}
85
86struct Member<T> {
87    join: Join<T>,
88    abort: AbortHandle,
89}
90
91/// A set of spawned tasks, joined in the order they finish.
92///
93/// Dropping the set cancels every task still in it, as dropping their
94/// [`Task`] handles would; [`detach_all`](Self::detach_all) lets them run on.
95///
96/// ```
97/// # nuclei::block_on(async {
98/// let mut set = nuclei::task::JoinSet::new();
99/// for i in 0..4u64 {
100///     set.spawn(async move { i * 10 });
101/// }
102/// let mut sum = 0;
103/// while let Some(out) = set.join_next().await {
104///     sum += out.unwrap();
105/// }
106/// assert_eq!(sum, 60);
107/// # });
108/// ```
109pub struct JoinSet<T> {
110    slots: Vec<Slot<T>>,
111    free: Vec<usize>,
112    len: usize,
113    shared: Arc<Shared>,
114}
115
116impl<T> Default for JoinSet<T> {
117    fn default() -> Self {
118        Self::new()
119    }
120}
121
122impl<T> JoinSet<T> {
123    /// An empty set.
124    pub fn new() -> Self {
125        Self {
126            slots: Vec::new(),
127            free: Vec::new(),
128            len: 0,
129            shared: Arc::new(Shared {
130                ready: Inbox::new(),
131                awaiter: AtomicWaker::new(),
132            }),
133        }
134    }
135
136    /// The tasks in the set, finished and not joined ones included.
137    pub fn len(&self) -> usize {
138        self.len
139    }
140
141    /// Whether the set holds no task.
142    pub fn is_empty(&self) -> bool {
143        self.len == 0
144    }
145
146    /// Takes `task` into the set and answers a handle that cancels it.
147    pub fn insert(&mut self, task: Task<T>) -> AbortHandle {
148        let abort = task.abort_handle();
149        let member = Member {
150            join: task.join(),
151            abort: abort.clone(),
152        };
153        let slot = match self.free.pop() {
154            Some(slot) => slot,
155            None => {
156                let slot = self.slots.len();
157                let wake = Arc::new(SlotWake {
158                    link: Link::new(),
159                    shared: Arc::downgrade(&self.shared),
160                    slot,
161                    queued: AtomicBool::new(false),
162                });
163                self.slots.push(Slot {
164                    waker: Waker::from(Arc::clone(&wake)),
165                    wake,
166                    member: None,
167                });
168                slot
169            }
170        };
171        let place = &mut self.slots[slot];
172        place.member = Some(member);
173        self.len += 1;
174        // Queued at once, unless a stale wake queued the slot already: the
175        // next `join_next` polls the new member.
176        if !place.wake.queued.swap(true, Ordering::AcqRel) {
177            self.shared.ready.push(Queued(Arc::clone(&place.wake)));
178        }
179        abort
180    }
181
182    /// Spawns `future` as [`spawn`](crate::task::spawn) does, into the set.
183    pub fn spawn<F>(&mut self, future: F) -> AbortHandle
184    where
185        F: Future<Output = T> + Send + 'static,
186        T: Send + 'static,
187    {
188        self.insert(crate::task::spawn(future))
189    }
190
191    /// Spawns `future` as [`spawn_local`](crate::task::spawn_local) does,
192    /// into the set.
193    pub fn spawn_local<F>(&mut self, future: F) -> AbortHandle
194    where
195        F: Future<Output = T> + 'static,
196        T: 'static,
197    {
198        self.insert(crate::task::spawn_local(future))
199    }
200
201    /// Runs `f` on the blocking pool, into the set.
202    pub fn spawn_blocking<F>(&mut self, f: F) -> AbortHandle
203    where
204        F: FnOnce() -> T + Send + 'static,
205        T: Send + 'static,
206    {
207        self.insert(crate::task::spawn_blocking(f))
208    }
209
210    /// Spawns `future` on the runtime `handle` refers to, into the set.
211    pub fn spawn_on<F>(&mut self, future: F, handle: &crate::runtime::RuntimeHandle) -> AbortHandle
212    where
213        F: Future<Output = T> + Send + 'static,
214        T: Send + 'static,
215    {
216        self.insert(handle.spawn(future))
217    }
218
219    /// The output of the next task to finish, `None` when the set is empty.
220    /// A task that was cancelled or panicked gives its [`JoinError`].
221    pub async fn join_next(&mut self) -> Option<Result<T, JoinError>> {
222        core::future::poll_fn(|cx| self.poll_join_next(cx))
223            .await
224            .map(|out| out.map(|(_, value)| value))
225    }
226
227    /// [`join_next`](Self::join_next), with the id of the task.
228    pub async fn join_next_with_id(&mut self) -> Option<Result<(Id, T), JoinError>> {
229        core::future::poll_fn(|cx| self.poll_join_next(cx)).await
230    }
231
232    /// The output of a task that has finished, without waiting: `None` when
233    /// none has (the set may still hold tasks).
234    pub fn try_join_next(&mut self) -> Option<Result<T, JoinError>> {
235        let mut cx = Context::from_waker(Waker::noop());
236        match self.poll_join_next(&mut cx) {
237            Poll::Ready(Some(out)) => Some(out.map(|(_, value)| value)),
238            _ => None,
239        }
240    }
241
242    /// Polls for the next finished task.
243    pub fn poll_join_next(
244        &mut self,
245        cx: &mut Context<'_>,
246    ) -> Poll<Option<Result<(Id, T), JoinError>>> {
247        if self.len == 0 {
248            return Poll::Ready(None);
249        }
250        self.shared.awaiter.register(cx.waker());
251        // At most one look at each member per poll: a member that keeps
252        // waking itself cannot hold the caller here.
253        for _ in 0..self.len {
254            // SAFETY: the set alone takes from its queue (`&mut self`).
255            let wake = match unsafe { self.shared.ready.pop() } {
256                Pop::Item(Queued(wake)) => wake,
257                // A wake between its two steps wakes the awaiter after it.
258                Pop::Empty | Pop::Busy => return Poll::Pending,
259            };
260            wake.queued.store(false, Ordering::Release);
261            let slot = wake.slot;
262            // A stale wake of an earlier set's slot (after `detach_all`).
263            let Some(place) = self
264                .slots
265                .get_mut(slot)
266                .filter(|p| Arc::ptr_eq(&p.wake, &wake))
267            else {
268                continue;
269            };
270            let Some(member) = place.member.as_mut() else {
271                continue;
272            };
273            let mut member_cx = Context::from_waker(&place.waker);
274            if let Poll::Ready(out) = Pin::new(&mut member.join).poll(&mut member_cx) {
275                let id = member.abort.id();
276                place.member = None;
277                self.free.push(slot);
278                self.len -= 1;
279                return Poll::Ready(Some(out.map(|value| (id, value))));
280            }
281        }
282        // Every look found an unfinished member that was woken again: give
283        // other tasks a turn and come back.
284        cx.waker().wake_by_ref();
285        Poll::Pending
286    }
287
288    /// Every task's output, in finishing order.
289    ///
290    /// # Panics
291    ///
292    /// When a task panicked (its panic is resumed) or was cancelled.
293    #[expect(clippy::panic, reason = "documented under `# Panics`")]
294    pub async fn join_all(mut self) -> Vec<T> {
295        let mut outputs = Vec::with_capacity(self.len);
296        while let Some(out) = self.join_next().await {
297            match out {
298                Ok(value) => outputs.push(value),
299                Err(error) => match error.try_into_panic() {
300                    Ok(panic) => std::panic::resume_unwind(panic),
301                    Err(error) => panic!("`JoinSet::join_all`: {error}"),
302                },
303            }
304        }
305        outputs
306    }
307
308    /// Cancels every task in the set; they stay in it, and `join_next` gives
309    /// each one's cancellation (or its output, if it finished first).
310    pub fn abort_all(&mut self) {
311        for member in self.slots.iter().filter_map(|p| p.member.as_ref()) {
312            member.abort.abort();
313        }
314    }
315
316    /// Takes every task out of the set and lets each run to its end. The
317    /// slots go with them, so a detached task's later wake meets none.
318    pub fn detach_all(&mut self) {
319        for place in self.slots.drain(..) {
320            if let Some(member) = place.member {
321                member.join.detach();
322            }
323        }
324        self.free.clear();
325        self.len = 0;
326        // SAFETY: the set alone takes from its queue (`&mut self`).
327        unsafe { self.shared.ready.drain(drop) };
328    }
329
330    /// Cancels every task and waits until each one is out of the set.
331    pub async fn shutdown(&mut self) {
332        self.abort_all();
333        while self.join_next().await.is_some() {}
334    }
335}
336
337impl<T, F> FromIterator<F> for JoinSet<T>
338where
339    F: Future<Output = T> + Send + 'static,
340    T: Send + 'static,
341{
342    fn from_iter<I: IntoIterator<Item = F>>(iter: I) -> Self {
343        let mut set = Self::new();
344        for future in iter {
345            set.spawn(future);
346        }
347        set
348    }
349}
350
351impl<T, F> Extend<F> for JoinSet<T>
352where
353    F: Future<Output = T> + Send + 'static,
354    T: Send + 'static,
355{
356    fn extend<I: IntoIterator<Item = F>>(&mut self, iter: I) {
357        for future in iter {
358            self.spawn(future);
359        }
360    }
361}
362
363impl<T> fmt::Debug for JoinSet<T> {
364    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365        f.debug_struct("JoinSet").field("len", &self.len).finish()
366    }
367}