Skip to main content

async_selector/
selector.rs

1//! Fast and flexible [`Future`]/[`Stream`] selector.
2
3use std::{
4    fmt,
5    marker::PhantomData,
6    ops::{ControlFlow, Not},
7    pin::Pin,
8    sync::Arc,
9    task::{Context, Poll},
10};
11
12use futures::Stream;
13
14use crate::{
15    list::{
16        IntrusiveList,
17        cursor::{Cursor, CursorMut},
18    },
19    mpsc,
20    pollable::{PollStrategy, PollWith},
21    selector::{
22        ext::{WithExt, WithExtAndId, WithId},
23        iter::{ExtractIf, IntoIter, Iter, IterMut},
24    },
25    task::Task,
26};
27
28pub use borrowed::{Borrowed, BorrowedMut};
29pub use id::Id;
30pub use removed::Removed;
31
32mod borrowed;
33pub mod ext;
34mod id;
35pub mod iter;
36mod removed;
37
38/// Selector over a dynamic set of pollable tasks, for example [`Future`]s and [`Stream`]s.
39///
40/// Designed for optimal performance when polling a large number of tasks
41/// (see [example](https://github.com/Razz4780/async-selector/blob/main/examples/speed.rs)).
42///
43/// Allows for:
44/// 1. Safely injecting shared state into the tasks (see [`PollWith`]).
45/// 2. Accessing and removing the tasks by unique ids.
46///
47/// Unless you want to exercise the full flexibility of this type,
48/// you can use the specialized [`FutureSelector`](crate::FutureSelector)
49/// and [`StreamSelector`](crate::StreamSelector).
50///
51/// # Removal
52///
53/// The selector creates a heap allocation for each stored task.
54/// Removing a task from the selector does not instantly free that memory.
55/// The memory can only be freed when:
56/// 1. all [`Id`] instances for this task are dropped, AND
57/// 2. [`Removed`] instance is dropped or consumed, AND
58/// 3. the [`Waker`](std::task::Waker) (and all its clones)
59///    passed when polling the task are dropped or consumed, AND
60/// 4. the selector observes the task removal.
61///
62/// The selector always eventually observes the removal when polled.
63///
64/// # Wakeups
65///
66/// The selector uses a smart strategy for polling the tasks.
67/// A task is **only** polled in the following cases:
68/// 1. after it is pushed into the selector
69/// 2. after it yields a non-terminal value
70/// 3. after the waker passed to [`PollWith::poll_progress`] inside [`Context`] is woken
71///
72/// To avoid nasty surprises, keep this in mind when:
73/// 1. Modifying a task borrowed from the selector
74/// 2. Polling the selector with different extension types
75///
76/// See [example](https://github.com/Razz4780/async-selector/blob/main/examples/extensions.rs).
77///
78/// # Panic
79///
80/// If the task's [`PollWith::poll_progress`] implementation panics,
81/// the task is removed from the selector and dropped.
82/// The selector remains valid.
83pub struct Selector<S: PollStrategy> {
84    /// Queue of tasks that were woken.
85    ready_rx: mpsc::Receiver<Task<S::Pollable>>,
86    /// List of all tasks.
87    list: IntrusiveList<Task<S::Pollable>>,
88    /// [`PollStrategy`] determining how we poll tasks.
89    _phantom: PhantomData<fn() -> S>,
90}
91
92impl<S: PollStrategy> Selector<S> {
93    /// Pushes a new task into the selector.
94    ///
95    /// This method is O(1).
96    pub fn push(&mut self, pollable: S::Pollable) {
97        let node = self
98            .list
99            .insert(Task::empty(self.ready_rx.weak_sender()), pollable);
100        self.ready_rx.send(node);
101    }
102
103    /// Pushes a new task into the selector and returns its unique id.
104    ///
105    /// This method is O(1).
106    pub fn push_with_id(&mut self, pollable: S::Pollable) -> Id<S::Pollable> {
107        let node = self
108            .list
109            .insert(Task::empty(self.ready_rx.weak_sender()), pollable);
110        let id = Id(Arc::downgrade(&node));
111        self.ready_rx.send(node);
112        id
113    }
114
115    /// Returns whether the selector is empty.
116    ///
117    /// This method is O(1).
118    pub fn is_empty(&self) -> bool {
119        self.list.is_empty()
120    }
121
122    /// Returns the number of tasks in the selector.
123    ///
124    /// This method is O(1).
125    pub fn len(&self) -> usize {
126        self.list.len()
127    }
128
129    /// Manually wakes all tasks in the selector.
130    ///
131    /// Depending on the tasks' [`PollWith`] implementation,
132    /// this might be required when polling with different extension types.
133    /// See the wakeups [section](Selector#wakeups).
134    ///
135    /// This method is O(n).
136    pub fn wake_all(&self) {
137        self.iter().for_each(|borrowed| borrowed.wake());
138    }
139
140    /// Returns an iterator over the tasks in the selector.
141    ///
142    /// The tasks are visited in the insertion order.
143    pub fn iter(&self) -> Iter<'_, S::Pollable> {
144        Iter {
145            cursor: Cursor::new(&self.list),
146            queue: &self.ready_rx,
147        }
148    }
149
150    /// Returns an iterator that allows modifying each task in the selector.
151    ///
152    /// The tasks are visited in the insertion order.
153    ///
154    /// **Important:** before modifying tasks stored in the selector, see the wakeups [section](Selector#wakeups).
155    pub fn iter_mut(&mut self) -> IterMut<'_, S::Pollable> {
156        IterMut {
157            cursor: CursorMut::new(&mut self.list),
158            queue: &self.ready_rx,
159        }
160    }
161
162    /// Creates an iterator which uses a closure to determine if a task should be removed.
163    ///
164    /// If the closure returns true, the task is removed from the selector and yielded.
165    ///
166    /// If the returned [`ExtractIf`] is not exhausted, e.g. because it is dropped without iterating or the iteration short-circuits,
167    /// then the remaining tasks will be retained.
168    ///
169    /// **Important:** before removing tasks from the selector, see the removal [section](Selector#removal).
170    #[must_use = "ExtractIf does not remove any elements unless consumed"]
171    pub fn extract_if<F>(&mut self, pred: F) -> ExtractIf<'_, S::Pollable, F>
172    where
173        F: FnMut(Pin<&mut S::Pollable>) -> bool,
174    {
175        ExtractIf {
176            cursor: CursorMut::new(&mut self.list),
177            pred,
178        }
179    }
180
181    /// Returns an immutable reference to the task with the given id.
182    ///
183    /// Returns `None` if the task is not found in this selector,
184    /// for example because it was removed or has already finished.
185    ///
186    /// This method is O(1).
187    pub fn get(&self, id: &Id<S::Pollable>) -> Option<Borrowed<'_, S::Pollable>> {
188        let task = id.0.upgrade()?;
189        if self.ready_rx.is_parent(task.ready_tx()).not() {
190            return None;
191        }
192        let node = unsafe {
193            // SAFETY: we just checked that this id comes from this selector.
194            // Therefore, the task cannot be stored in any other list.
195            self.list.get(&task)
196        }?;
197        Some(Borrowed {
198            node,
199            queue: &self.ready_rx,
200        })
201    }
202
203    /// Returns a mutable reference to the target with the given id.
204    ///
205    /// Returns `None` if the task is not found in this selector,
206    /// for example because it was removed or has already finished.
207    ///
208    /// This method is O(1).
209    ///
210    /// **Important:** before modifying tasks stored in the selector, see the wakeups [section](Selector#wakeups).
211    pub fn get_mut(&mut self, id: &Id<S::Pollable>) -> Option<BorrowedMut<'_, S::Pollable>> {
212        let task = id.0.upgrade()?;
213        if self.ready_rx.is_parent(task.ready_tx()).not() {
214            return None;
215        }
216        let node = unsafe {
217            // SAFETY: we just checked that this id comes from this selector.
218            // Therefore, the task cannot be stored in any other list.
219            self.list.get_mut(&task)
220        }?;
221        Some(BorrowedMut {
222            node,
223            queue: &self.ready_rx,
224        })
225    }
226
227    /// Removes the task with the given id from the selector.
228    ///
229    /// Returns `None` if the task is not found in the selector,
230    /// for example because it was removed or has already finished.
231    ///
232    /// This method is O(1).
233    ///
234    /// **Important:** before removing tasks from the selector, see the removal [section](Selector#removal).
235    pub fn remove(&mut self, id: &Id<S::Pollable>) -> Option<Removed<S::Pollable>> {
236        let task = id.0.upgrade()?;
237        if self.ready_rx.is_parent(task.ready_tx()).not() {
238            return None;
239        }
240        let removed = unsafe {
241            // SAFETY: we just checked that this id comes from this selector.
242            // Therefore, the task cannot be stored in any other list.
243            self.list.remove(&task)?
244        };
245        Some(Removed(removed))
246    }
247
248    /// Borrows a [`Stream`] that will pass the given extensions to the tasks when polling.
249    ///
250    /// **Important:** before polling the tasks with different extension types, see the wakeups [section](Selector#wakeups).
251    pub fn with_ext<'s, 'e, 'emut, E: ?Sized, EMut: ?Sized>(
252        &'s mut self,
253        ext: &'e E,
254        ext_mut: &'emut mut EMut,
255    ) -> WithExt<'s, 'e, 'emut, S, E, EMut> {
256        WithExt {
257            selector: self,
258            ext,
259            ext_mut,
260        }
261    }
262
263    /// Borrows a [`Stream`] that will return a task [`Id`] with every item.
264    pub fn with_id(&mut self) -> WithId<'_, S> {
265        WithId { selector: self }
266    }
267
268    /// Borrows a [`Stream`] that will pass the given extensions to the tasks when polling,
269    /// and return a task [`Id`] with every item.
270    ///
271    /// **Important:** before polling the tasks with different extension types, see the wakeups [section](Selector#wakeups).
272    pub fn with_ext_and_id<'s, 'e, 'emut, E: ?Sized, EMut: ?Sized>(
273        &'s mut self,
274        ext: &'e E,
275        ext_mut: &'emut mut EMut,
276    ) -> WithExtAndId<'s, 'e, 'emut, S, E, EMut> {
277        WithExtAndId {
278            selector: self,
279            ext,
280            ext_mut,
281        }
282    }
283
284    /// Returns the next ready item from one of the tasks stored in the selector.
285    ///
286    /// Provided extensions will be passed down to the tasks as arguments to [`PollWith::poll_progress`].
287    /// Provided `extra` lambda can be used to extract additional data from the [`Task`] instance.
288    ///
289    /// Returns `None` if the selector is empty.
290    #[allow(clippy::type_complexity)]
291    fn poll_next_inner<'a, E, EMut, F, T>(
292        &mut self,
293        ext: &'a E,
294        ext_mut: &mut EMut,
295        extra: F,
296        cx: &mut Context<'_>,
297    ) -> Poll<Option<(<S as PollWith<'a, E, EMut>>::Progress, T)>>
298    where
299        S: PollWith<'a, E, EMut>,
300        E: ?Sized,
301        EMut: ?Sized,
302        F: FnOnce(&Arc<Task<S::Pollable>>) -> T,
303    {
304        let marker = self.ready_rx.register(cx.waker());
305        if marker.is_null() {
306            return if self.list.is_empty() {
307                Poll::Ready(None)
308            } else {
309                Poll::Pending
310            };
311        }
312
313        let mut polled_all_queue = false;
314        while polled_all_queue.not() {
315            let task = match self.ready_rx.recv() {
316                Some(task) => {
317                    polled_all_queue = std::ptr::eq(task.as_ref(), marker);
318                    task
319                }
320                None if self.list.is_empty() => return Poll::Ready(None),
321                None => return Poll::Pending,
322            };
323
324            let mut guard = {
325                let guard = unsafe {
326                    // SAFETY: we received this task from our ready queue,
327                    // so it must be ours.
328                    self.list.access(&task)
329                };
330                match guard {
331                    Some(guard) => guard,
332                    None => continue,
333                }
334            };
335            let waker = task.borrow_waker();
336            let mut cx = Context::from_waker(&waker);
337
338            // If this method panics, the guard will automatically remove the task from the list and drop it.
339            let result = S::poll_progress(guard.get(), ext, ext_mut, &mut cx);
340
341            match result {
342                Poll::Ready(ControlFlow::Continue(item)) => {
343                    guard.forget();
344                    let extra = extra(&task);
345                    self.ready_rx.send(task);
346                    return Poll::Ready(Some((item, extra)));
347                }
348                Poll::Ready(ControlFlow::Break(Some(item))) => {
349                    let extra = extra(&task);
350                    return Poll::Ready(Some((item, extra)));
351                }
352                Poll::Ready(ControlFlow::Break(None)) => {}
353                Poll::Pending => guard.forget(),
354            }
355        }
356
357        if self.list.is_empty() {
358            Poll::Ready(None)
359        } else {
360            Poll::Pending
361        }
362    }
363}
364
365impl<S: PollWith<'static, (), ()>> Stream for Selector<S> {
366    type Item = S::Progress;
367
368    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
369        let this = unsafe {
370            // SAFETY: no field is ever moved in memory.
371            self.get_unchecked_mut()
372        };
373        this.poll_next_inner(&(), &mut (), |_| (), cx)
374            .map(|opt| opt.map(|(item, ())| item))
375    }
376}
377
378impl<S: PollStrategy> Default for Selector<S> {
379    fn default() -> Self {
380        Self {
381            ready_rx: mpsc::Receiver::new(Task::empty),
382            list: Default::default(),
383            _phantom: Default::default(),
384        }
385    }
386}
387
388impl<S: PollStrategy> fmt::Debug for Selector<S> {
389    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390        f.debug_struct("Selector")
391            .field("len", &self.len())
392            .finish()
393    }
394}
395
396impl<S: PollStrategy> Extend<S::Pollable> for Selector<S> {
397    fn extend<T: IntoIterator<Item = S::Pollable>>(&mut self, iter: T) {
398        for pollable in iter {
399            self.push(pollable);
400        }
401    }
402}
403
404impl<S: PollStrategy> FromIterator<S::Pollable> for Selector<S> {
405    fn from_iter<T: IntoIterator<Item = S::Pollable>>(iter: T) -> Self {
406        let mut this = Self::default();
407        this.extend(iter);
408        this
409    }
410}
411
412impl<S: PollStrategy> IntoIterator for Selector<S> {
413    type IntoIter = IntoIter<S::Pollable>;
414    type Item = Removed<S::Pollable>;
415
416    fn into_iter(self) -> Self::IntoIter {
417        IntoIter(self.list)
418    }
419}
420
421#[cfg(test)]
422mod test {
423    use std::{
424        ops::Not,
425        panic::{AssertUnwindSafe, catch_unwind},
426        pin::Pin,
427        sync::Arc,
428        task::{Context, Poll, Waker},
429    };
430
431    use futures::{FutureExt, StreamExt, channel::oneshot, task::AtomicWaker};
432    use rstest::rstest;
433
434    use crate::{pollable::PollAsFuture, selector::Selector};
435
436    #[tokio::test]
437    async fn basic() {
438        let (tx, rx) = oneshot::channel::<()>();
439        let mut selector = Selector::<PollAsFuture<_>>::default();
440        selector.push(rx);
441        assert!(selector.next().now_or_never().is_none());
442        assert_eq!(selector.len(), 1);
443        tx.send(()).unwrap();
444        assert!(selector.next().await.is_some());
445        assert_eq!(selector.len(), 0);
446    }
447
448    /// Verifies that [`Selector`] respects the inner item's yield when polled,
449    /// and does not poll the same item twice in a single [`Selector::poll_next_with_ext`] call.
450    #[rstest]
451    #[tokio::test]
452    async fn task_yield_is_respected(#[values(1, 4, 8)] futures: usize) {
453        struct Fut {
454            polled: bool,
455        }
456
457        impl Future for Fut {
458            type Output = ();
459
460            fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
461                let this = self.get_mut();
462                if this.polled.not() {
463                    this.polled = true;
464                    cx.waker().wake_by_ref();
465                    Poll::Pending
466                } else {
467                    Poll::Ready(())
468                }
469            }
470        }
471
472        let mut selector = Selector::<PollAsFuture<_>>::default();
473        for _ in 0..futures {
474            selector.push(Fut { polled: false });
475        }
476
477        assert!(
478            selector
479                .poll_next_unpin(&mut Context::from_waker(Waker::noop()))
480                .is_pending()
481        );
482        for fut in selector.iter() {
483            assert!(fut.polled);
484        }
485
486        for _ in 0..futures {
487            assert_eq!(
488                selector.poll_next_unpin(&mut Context::from_waker(Waker::noop())),
489                Poll::Ready(Some(())),
490            );
491        }
492    }
493
494    #[test]
495    fn stale_wakeups_on_removed_tasks_still_report_empty_selector() {
496        struct StoreWaker(Arc<AtomicWaker>);
497
498        impl Future for StoreWaker {
499            type Output = usize;
500
501            fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
502                self.0.register(cx.waker());
503                Poll::Pending
504            }
505        }
506
507        let slot = Arc::new(AtomicWaker::new());
508        let mut selector = Selector::<PollAsFuture<_>>::default();
509        let id = selector.push_with_id(StoreWaker(slot.clone()).boxed());
510        let mut cx = Context::from_waker(Waker::noop());
511
512        assert!(selector.poll_next_unpin(&mut cx).is_pending());
513        let waker = slot.take().unwrap();
514        let _ = selector.remove(&id).unwrap();
515        assert!(selector.is_empty());
516
517        for _ in 0..3 {
518            waker.wake_by_ref();
519            assert_eq!(selector.poll_next_unpin(&mut cx), Poll::Ready(None));
520        }
521
522        selector.push(std::future::ready(7).boxed());
523        assert_eq!(selector.poll_next_unpin(&mut cx), Poll::Ready(Some(7)));
524        assert_eq!(selector.poll_next_unpin(&mut cx), Poll::Ready(None));
525    }
526
527    #[test]
528    fn panicking_task_is_removed_and_selector_remains_valid() {
529        struct PanicOnPoll {
530            _shared: Arc<()>,
531        }
532
533        impl Future for PanicOnPoll {
534            type Output = usize;
535
536            fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
537                panic!("boom");
538            }
539        }
540
541        let drops = Arc::new(());
542        let drops_weak = Arc::downgrade(&drops);
543        let mut selector = Selector::<PollAsFuture<_>>::default();
544        selector.push(PanicOnPoll { _shared: drops }.boxed());
545
546        let mut cx = Context::from_waker(Waker::noop());
547        let result = catch_unwind(AssertUnwindSafe(|| selector.poll_next_unpin(&mut cx)));
548
549        assert!(result.is_err());
550        assert!(selector.is_empty());
551        assert_eq!(selector.len(), 0);
552        assert!(drops_weak.upgrade().is_none());
553
554        selector.push(std::future::ready(11).boxed());
555        assert_eq!(selector.poll_next_unpin(&mut cx), Poll::Ready(Some(11)));
556        assert_eq!(selector.poll_next_unpin(&mut cx), Poll::Ready(None));
557    }
558}