Skip to main content

futures_bounded/
futures_map.rs

1use std::any::Any;
2use std::future::Future;
3use std::hash::Hash;
4use std::pin::Pin;
5use std::task::{Context, Poll, Waker};
6use std::time::Duration;
7use std::{future, mem};
8
9use futures_util::stream::FuturesUnordered;
10use futures_util::{FutureExt, StreamExt};
11
12use crate::{AnyFuture, BoxFuture, Delay, PushError, Timeout};
13
14/// Represents a map of [`Future`]s.
15///
16/// Each future must finish within the specified time and the map never outgrows its capacity.
17pub struct FuturesMap<ID, O> {
18    make_delay: Box<dyn Fn() -> Delay + Send + Sync>,
19    capacity: usize,
20    inner: FuturesUnordered<TaggedFuture<ID, TimeoutFuture<BoxFuture<O>>>>,
21    empty_waker: Option<Waker>,
22    full_waker: Option<Waker>,
23}
24
25impl<ID, O> FuturesMap<ID, O> {
26    pub fn new(make_delay: impl Fn() -> Delay + Send + Sync + 'static, capacity: usize) -> Self {
27        Self {
28            make_delay: Box::new(make_delay),
29            capacity,
30            inner: Default::default(),
31            empty_waker: None,
32            full_waker: None,
33        }
34    }
35}
36
37impl<ID, O> FuturesMap<ID, O>
38where
39    ID: Clone + Hash + Eq + Send + Unpin + 'static,
40    O: 'static,
41{
42    /// Push a future into the map.
43    ///
44    /// This method inserts the given future with defined `future_id` to the set.
45    /// If the length of the map is equal to the capacity, this method returns [PushError::BeyondCapacity],
46    /// that contains the passed future. In that case, the future is not inserted to the map.
47    /// If a future with the given `future_id` already exists, then the old future will be replaced by a new one.
48    /// In that case, the returned error [PushError::Replaced] contains the old future.
49    pub fn try_push<F>(&mut self, future_id: ID, future: F) -> Result<(), PushError<BoxFuture<O>>>
50    where
51        F: AnyFuture<Output = O>,
52    {
53        if self.inner.len() >= self.capacity {
54            return Err(PushError::BeyondCapacity(Box::pin(future)));
55        }
56
57        if let Some(waker) = self.empty_waker.take() {
58            waker.wake();
59        }
60
61        let old = self.remove(future_id.clone());
62        self.inner.push(TaggedFuture {
63            tag: future_id,
64            inner: TimeoutFuture {
65                inner: Box::pin(future),
66                timeout: (self.make_delay)(),
67                cancelled: false,
68            },
69        });
70        match old {
71            None => Ok(()),
72            Some(old) => Err(PushError::Replaced(old)),
73        }
74    }
75
76    pub fn remove(&mut self, id: ID) -> Option<BoxFuture<O>> {
77        let tagged = self.inner.iter_mut().find(|s| s.tag == id)?;
78
79        let inner = mem::replace(&mut tagged.inner.inner, Box::pin(future::pending()));
80        tagged.inner.cancelled = true;
81
82        Some(inner)
83    }
84
85    pub fn contains(&self, id: ID) -> bool {
86        self.inner.iter().any(|f| f.tag == id && !f.inner.cancelled)
87    }
88
89    pub fn len(&self) -> usize {
90        self.inner.len()
91    }
92
93    pub fn is_empty(&self) -> bool {
94        self.inner.is_empty()
95    }
96
97    #[allow(unknown_lints, clippy::needless_pass_by_ref_mut)] // &mut Context is idiomatic.
98    pub fn poll_ready_unpin(&mut self, cx: &mut Context<'_>) -> Poll<()> {
99        if self.inner.len() < self.capacity {
100            return Poll::Ready(());
101        }
102
103        self.full_waker = Some(cx.waker().clone());
104
105        Poll::Pending
106    }
107
108    pub fn poll_unpin(&mut self, cx: &mut Context<'_>) -> Poll<(ID, Result<O, Timeout>)> {
109        loop {
110            let maybe_result = futures_util::ready!(self.inner.poll_next_unpin(cx));
111
112            match maybe_result {
113                None => {
114                    self.empty_waker = Some(cx.waker().clone());
115                    return Poll::Pending;
116                }
117                Some((id, Ok(output))) => return Poll::Ready((id, Ok(output))),
118                Some((id, Err(TimeoutError::Timeout(dur)))) => {
119                    return Poll::Ready((id, Err(Timeout::new(dur))))
120                }
121                Some((_, Err(TimeoutError::Cancelled))) => continue,
122            }
123        }
124    }
125
126    /// Returns an iterator over all futures of type `T` pushed via [`FuturesMap::try_push`].
127    /// This iterator returns futures in an arbitrary order, which may change.
128    ///
129    /// If downcasting a future to `T` fails it will be skipped in the iterator.
130    pub fn iter_of_type<T>(&self) -> impl Iterator<Item = (&ID, Pin<&T>)>
131    where
132        T: 'static,
133    {
134        self.inner.iter().filter_map(|a| {
135            let pointer = a.inner.inner.as_ref().get_ref();
136
137            let any = pointer as &(dyn Any + Send);
138            // SAFETY: this returns `None` and drops a `&T`, which is safe because dropping a reference is trivial.
139            let inner = any.downcast_ref::<T>()?;
140
141            // Safety: The pointer is already pinned, and will remain pinned for its entire lifetime,
142            // because we return a `Pin<&T>`.
143            let pinned = unsafe { Pin::new_unchecked(inner) };
144
145            Some((&a.tag, pinned))
146        })
147    }
148
149    /// Returns an iterator with mutable access over all futures of type `T` pushed via
150    /// [`FuturesMap::try_push`].
151    /// This iterator returns futures in an arbitrary order, which may change.
152    ///
153    /// If downcasting a future to `T` fails it will be skipped in the iterator.
154    pub fn iter_mut_of_type<T>(&mut self) -> impl Iterator<Item = (&ID, Pin<&mut T>)>
155    where
156        T: 'static,
157    {
158        self.inner.iter_mut().filter_map(|a| {
159            let pin = a.inner.inner.as_mut();
160
161            // Safety: We are only temporarily manipulating the pointer and pinning it again further down.
162            let pointer = unsafe { Pin::into_inner_unchecked(pin) };
163            let any = pointer as &mut (dyn Any + Send);
164            let inner = any.downcast_mut::<T>()?;
165
166            // Safety: The pointer is already pinned.
167            let pinned = unsafe { Pin::new_unchecked(inner) };
168
169            Some((&a.tag, pinned))
170        })
171    }
172}
173
174struct TimeoutFuture<F> {
175    inner: F,
176    timeout: Delay,
177
178    cancelled: bool,
179}
180
181impl<F> Future for TimeoutFuture<F>
182where
183    F: Future + Unpin,
184{
185    type Output = Result<F::Output, TimeoutError>;
186
187    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
188        if self.cancelled {
189            return Poll::Ready(Err(TimeoutError::Cancelled));
190        }
191
192        if let Poll::Ready(duration) = self.timeout.poll_unpin(cx) {
193            return Poll::Ready(Err(TimeoutError::Timeout(duration)));
194        }
195
196        self.inner.poll_unpin(cx).map(Ok)
197    }
198}
199
200enum TimeoutError {
201    Timeout(Duration),
202    Cancelled,
203}
204
205struct TaggedFuture<T, F> {
206    tag: T,
207    inner: F,
208}
209
210impl<T, F> Future for TaggedFuture<T, F>
211where
212    T: Clone + Unpin,
213    F: Future + Unpin,
214{
215    type Output = (T, F::Output);
216
217    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
218        let output = futures_util::ready!(self.inner.poll_unpin(cx));
219
220        Poll::Ready((self.tag.clone(), output))
221    }
222}
223
224#[cfg(all(test, feature = "futures-timer"))]
225mod tests {
226    use futures::channel::oneshot;
227    use futures_util::task::noop_waker_ref;
228    use std::future::{pending, poll_fn, ready};
229    use std::pin::Pin;
230    use std::time::Instant;
231
232    use super::*;
233
234    #[test]
235    fn cannot_push_more_than_capacity_tasks() {
236        let mut futures = FuturesMap::new(|| Delay::futures_timer(Duration::from_secs(10)), 1);
237
238        assert!(futures.try_push("ID_1", ready(())).is_ok());
239        matches!(
240            futures.try_push("ID_2", ready(())),
241            Err(PushError::BeyondCapacity(_))
242        );
243    }
244
245    #[test]
246    fn cannot_push_the_same_id_few_times() {
247        let mut futures = FuturesMap::new(|| Delay::futures_timer(Duration::from_secs(10)), 5);
248
249        assert!(futures.try_push("ID", ready(())).is_ok());
250        matches!(
251            futures.try_push("ID", ready(())),
252            Err(PushError::Replaced(_))
253        );
254    }
255
256    #[tokio::test]
257    async fn futures_timeout() {
258        let mut futures = FuturesMap::new(|| Delay::futures_timer(Duration::from_millis(100)), 1);
259
260        let _ = futures.try_push("ID", pending::<()>());
261        futures_timer::Delay::new(Duration::from_millis(150)).await;
262        let (_, result) = poll_fn(|cx| futures.poll_unpin(cx)).await;
263
264        assert!(result.is_err())
265    }
266
267    #[test]
268    fn resources_of_removed_future_are_cleaned_up() {
269        let mut futures = FuturesMap::new(|| Delay::futures_timer(Duration::from_millis(100)), 1);
270
271        let _ = futures.try_push("ID", pending::<()>());
272        futures.remove("ID");
273
274        let poll = futures.poll_unpin(&mut Context::from_waker(noop_waker_ref()));
275        assert!(poll.is_pending());
276
277        assert_eq!(futures.len(), 0);
278    }
279
280    #[tokio::test]
281    async fn replaced_pending_future_is_polled() {
282        let mut streams = FuturesMap::new(|| Delay::futures_timer(Duration::from_millis(100)), 3);
283
284        let (_tx1, rx1) = oneshot::channel();
285        let (tx2, rx2) = oneshot::channel();
286
287        let _ = streams.try_push("ID1", rx1);
288        let _ = streams.try_push("ID2", rx2);
289
290        let _ = tx2.send(2);
291        let (id, res) = poll_fn(|cx| streams.poll_unpin(cx)).await;
292        assert_eq!(id, "ID2");
293        assert_eq!(res.unwrap().unwrap(), 2);
294
295        let (new_tx1, new_rx1) = oneshot::channel();
296        let replaced = streams.try_push("ID1", new_rx1);
297        assert!(matches!(replaced.unwrap_err(), PushError::Replaced(_)));
298
299        let _ = new_tx1.send(4);
300        let (id, res) = poll_fn(|cx| streams.poll_unpin(cx)).await;
301
302        assert_eq!(id, "ID1");
303        assert_eq!(res.unwrap().unwrap(), 4);
304    }
305
306    // Each future causes a delay, `Task` only has a capacity of 1, meaning they must be processed in sequence.
307    // We stop after NUM_FUTURES tasks, meaning the overall execution must at least take DELAY * NUM_FUTURES.
308    #[tokio::test]
309    async fn backpressure() {
310        const DELAY: Duration = Duration::from_millis(100);
311        const NUM_FUTURES: u32 = 10;
312
313        let start = Instant::now();
314        Task::new(DELAY, NUM_FUTURES, 1).await;
315        let duration = start.elapsed();
316
317        assert!(duration >= DELAY * NUM_FUTURES);
318    }
319
320    #[test]
321    fn contains() {
322        let mut futures = FuturesMap::new(|| Delay::futures_timer(Duration::from_secs(10)), 1);
323        _ = futures.try_push("ID", pending::<()>());
324        assert!(futures.contains("ID"));
325        _ = futures.remove("ID");
326        assert!(!futures.contains("ID"));
327    }
328
329    #[test]
330    fn can_iter_typed_futures() {
331        const N: usize = 10;
332        let mut futures = FuturesMap::new(|| Delay::futures_timer(Duration::from_millis(100)), N);
333        let mut sender = Vec::with_capacity(N);
334        for i in 0..N {
335            let (tx, rx) = oneshot::channel::<()>();
336            futures.try_push(format!("ID{i}"), rx).unwrap();
337            sender.push(tx);
338        }
339        assert_eq!(futures.iter_of_type::<oneshot::Receiver<()>>().count(), N);
340        for (i, (id, _)) in futures.iter_of_type::<oneshot::Receiver<()>>().enumerate() {
341            let expect_id = format!("ID{}", N - i - 1); // Reverse order.
342            assert_eq!(id, &expect_id);
343        }
344        assert!(!sender.iter().any(|tx| tx.is_canceled()));
345
346        for (_, mut rx) in futures.iter_mut_of_type::<oneshot::Receiver<()>>() {
347            rx.close();
348        }
349        assert!(sender.iter().all(|tx| tx.is_canceled()));
350
351        // Deliberately try a non-matching type
352        assert_eq!(futures.iter_mut_of_type::<()>().count(), 0);
353    }
354
355    struct Task {
356        future: Duration,
357        num_futures: usize,
358        num_processed: usize,
359        inner: FuturesMap<u8, ()>,
360    }
361
362    impl Task {
363        fn new(future: Duration, num_futures: u32, capacity: usize) -> Self {
364            Self {
365                future,
366                num_futures: num_futures as usize,
367                num_processed: 0,
368                inner: FuturesMap::new(|| Delay::futures_timer(Duration::from_secs(60)), capacity),
369            }
370        }
371    }
372
373    impl Future for Task {
374        type Output = ();
375
376        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
377            let this = self.get_mut();
378
379            while this.num_processed < this.num_futures {
380                if let Poll::Ready((_, result)) = this.inner.poll_unpin(cx) {
381                    if result.is_err() {
382                        panic!("Timeout is great than future delay")
383                    }
384
385                    this.num_processed += 1;
386                    continue;
387                }
388
389                if let Poll::Ready(()) = this.inner.poll_ready_unpin(cx) {
390                    // We push the constant future's ID to prove that user can use the same ID
391                    // if the future was finished
392                    let maybe_future = this
393                        .inner
394                        .try_push(1u8, futures_timer::Delay::new(this.future));
395                    assert!(maybe_future.is_ok(), "we polled for readiness");
396
397                    continue;
398                }
399
400                return Poll::Pending;
401            }
402
403            Poll::Ready(())
404        }
405    }
406}