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