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