Skip to main content

omaha_client/
async_generator.rs

1// Copyright 2020 The Fuchsia Authors
2//
3// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6// This file may not be copied, modified, or distributed except according to
7// those terms.
8
9#![deny(missing_docs)]
10#![allow(clippy::let_unit_value)]
11
12//! Asynchronous generator-like functionality in stable Rust.
13
14use {
15    futures::{
16        channel::mpsc,
17        future::FusedFuture,
18        prelude::*,
19        stream::FusedStream,
20        task::{Context, Poll},
21    },
22    pin_project::pin_project,
23    std::pin::Pin,
24};
25
26/// Produces an asynchronous `Stream` of [`GeneratorState<I, R>`] by invoking the given closure
27/// with a handle that can be used to yield items.
28///
29/// The returned `Stream` will produce a GeneratorState::Yielded variant for all yielded items
30/// from the asynchronous task, followed by a single GeneratorState::Complete variant, which will
31/// always be present as the final element in the stream.
32pub fn generate<'a, I, R, C, F>(cb: C) -> Generator<F, I, R>
33where
34    C: FnOnce(Yield<I>) -> F,
35    F: Future<Output = R> + 'a,
36    I: Send + 'static,
37    R: Send + 'static,
38{
39    let (send, recv) = mpsc::channel(0);
40    Generator { task: cb(Yield(send)).fuse(), stream: recv, res: None }
41}
42
43/// Control handle to yield items to the coroutine.
44pub struct Yield<I>(mpsc::Sender<I>);
45
46impl<I> Yield<I>
47where
48    I: Send + 'static,
49{
50    /// Yield a single item to the coroutine, waiting for it to receive the item.
51    pub fn yield_(&mut self, item: I) -> impl Future<Output = ()> + '_ {
52        // Ignore errors as Generator never drops the stream before the task.
53        self.0.send(item).map(|_| ())
54    }
55
56    /// Yield multiple items to the coroutine, waiting for it to receive all of them.
57    pub fn yield_all<S>(&mut self, items: S) -> impl Future<Output = ()> + '_
58    where
59        S: IntoIterator<Item = I>,
60        S::IntoIter: 'static,
61    {
62        let mut items = futures::stream::iter(items.into_iter().map(Ok));
63        async move {
64            let _ = self.0.send_all(&mut items).await;
65        }
66    }
67}
68
69/// Emitted state from an async generator.
70#[derive(Debug, PartialEq, Eq)]
71pub enum GeneratorState<I, R> {
72    /// The async generator yielded a value.
73    Yielded(I),
74
75    /// The async generator completed with a return value.
76    Complete(R),
77}
78
79impl<I, R> GeneratorState<I, R> {
80    fn into_yielded(self) -> Option<I> {
81        match self {
82            GeneratorState::Yielded(item) => Some(item),
83            _ => None,
84        }
85    }
86
87    fn into_complete(self) -> Option<R> {
88        match self {
89            GeneratorState::Complete(res) => Some(res),
90            _ => None,
91        }
92    }
93}
94
95/// An asynchronous generator.
96#[pin_project]
97#[derive(Debug)]
98pub struct Generator<F, I, R>
99where
100    F: Future<Output = R>,
101{
102    #[pin]
103    task: future::Fuse<F>,
104    #[pin]
105    stream: mpsc::Receiver<I>,
106    res: Option<R>,
107}
108
109impl<F, I, E> Generator<F, I, Result<(), E>>
110where
111    F: Future<Output = Result<(), E>>,
112{
113    /// Transforms this stream of `GeneratorState<I, Result<(), E>>` into a stream of `Result<I, E>`.
114    pub fn into_try_stream(self) -> impl FusedStream<Item = Result<I, E>> {
115        self.filter_map(|state| {
116            future::ready(match state {
117                GeneratorState::Yielded(i) => Some(Ok(i)),
118                GeneratorState::Complete(Ok(())) => None,
119                GeneratorState::Complete(Err(e)) => Some(Err(e)),
120            })
121        })
122    }
123}
124
125impl<F, I, R> Generator<F, I, R>
126where
127    F: Future<Output = R>,
128{
129    /// Discards all intermediate values produced by this generator, producing just the final result.
130    pub async fn into_complete(self) -> R {
131        let s = self.filter_map(|state| future::ready(state.into_complete()));
132        futures::pin_mut!(s);
133
134        // Generators always yield a complete item as the final element once the task
135        // completes.
136        s.next().await.unwrap()
137    }
138}
139
140impl<F, I> Generator<F, I, ()>
141where
142    F: Future<Output = ()>,
143{
144    /// Filters the states produced by this generator to only include intermediate yielded values,
145    /// discarding the final result.
146    pub fn into_yielded(self) -> impl FusedStream<Item = I> {
147        self.filter_map(|state| future::ready(state.into_yielded()))
148    }
149}
150
151impl<F, I, R> Stream for Generator<F, I, R>
152where
153    F: Future<Output = R>,
154{
155    type Item = GeneratorState<I, R>;
156
157    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
158        let this = self.project();
159
160        // Always poll the task first to make forward progress and maybe push an item into the
161        // channel.
162        let mut task_done = this.task.is_terminated();
163        if let Poll::Ready(res) = this.task.poll(cx) {
164            // This stream might not be ready for the final result yet, store it for later.
165            this.res.replace(res);
166            task_done = true;
167        }
168
169        // Return anything available from the stream, ignoring stream termination to let the task
170        // termination yield the last value.
171        if !this.stream.is_terminated() {
172            match this.stream.poll_next(cx) {
173                Poll::Pending => return Poll::Pending,
174                Poll::Ready(Some(item)) => return Poll::Ready(Some(GeneratorState::Yielded(item))),
175                Poll::Ready(None) => {}
176            }
177        }
178
179        if !task_done {
180            return Poll::Pending;
181        }
182
183        // Flush the final result once all tasks are done.
184        match this.res.take() {
185            Some(res) => Poll::Ready(Some(GeneratorState::Complete(res))),
186            None => Poll::Ready(None),
187        }
188    }
189}
190
191impl<F, I, R> FusedStream for Generator<F, I, R>
192where
193    F: Future<Output = R>,
194{
195    fn is_terminated(&self) -> bool {
196        self.task.is_terminated() && self.stream.is_terminated() && self.res.is_none()
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203    use futures::executor::block_on;
204    use std::sync::atomic;
205
206    /// Returns a future that yields to the executor once before completing.
207    fn yield_once() -> impl Future<Output = ()> {
208        let mut done = false;
209        future::poll_fn(move |cx: &mut Context<'_>| {
210            if !done {
211                done = true;
212                cx.waker().wake_by_ref();
213                Poll::Pending
214            } else {
215                Poll::Ready(())
216            }
217        })
218    }
219
220    #[derive(Debug, Default)]
221    struct Counter(atomic::AtomicU32);
222
223    impl Counter {
224        fn inc(&self) {
225            self.0.fetch_add(1, atomic::Ordering::SeqCst);
226        }
227
228        fn take(&self) -> u32 {
229            self.0.swap(0, atomic::Ordering::SeqCst)
230        }
231    }
232
233    #[test]
234    fn generator_waits_for_item_to_yield() {
235        let counter = Counter::default();
236
237        let s = generate(|mut co| {
238            let counter = &counter;
239            async move {
240                counter.inc();
241                co.yield_("first").await;
242
243                // This yield should not be observable by the stream, but the extra increment will
244                // be.
245                counter.inc();
246                yield_once().await;
247
248                counter.inc();
249                co.yield_("second").await;
250
251                drop(co);
252                yield_once().await;
253
254                counter.inc();
255            }
256        });
257
258        block_on(async {
259            futures::pin_mut!(s);
260
261            assert_eq!(counter.take(), 0);
262
263            assert_eq!(s.next().await, Some(GeneratorState::Yielded("first")));
264            assert_eq!(counter.take(), 1);
265
266            assert_eq!(s.next().await, Some(GeneratorState::Yielded("second")));
267            assert_eq!(counter.take(), 2);
268
269            assert_eq!(s.next().await, Some(GeneratorState::Complete(())));
270            assert_eq!(counter.take(), 1);
271
272            assert_eq!(s.next().await, None);
273            assert_eq!(counter.take(), 0);
274        });
275    }
276
277    #[test]
278    fn yield_all_yields_all() {
279        let s = generate(|mut co| async move {
280            co.yield_all(1u32..4).await;
281            co.yield_(42).await;
282        });
283
284        let res = block_on(s.collect::<Vec<GeneratorState<u32, ()>>>());
285
286        assert_eq!(
287            res,
288            vec![
289                GeneratorState::Yielded(1),
290                GeneratorState::Yielded(2),
291                GeneratorState::Yielded(3),
292                GeneratorState::Yielded(42),
293                GeneratorState::Complete(()),
294            ]
295        );
296    }
297
298    #[test]
299    fn fused_impl() {
300        let s = generate(|mut co| async move {
301            co.yield_(1u32).await;
302            drop(co);
303
304            yield_once().await;
305
306            "done"
307        });
308
309        block_on(async {
310            futures::pin_mut!(s);
311
312            assert!(!s.is_terminated());
313            assert_eq!(s.next().await, Some(GeneratorState::Yielded(1)));
314
315            assert!(!s.is_terminated());
316            assert_eq!(s.next().await, Some(GeneratorState::Complete("done")));
317
318            // FusedStream's is_terminated typically returns false after yielding None to indicate
319            // no items are left, but it is also valid to return true when the stream is going to
320            // not make further progress.
321            assert!(s.is_terminated());
322            assert_eq!(s.next().await, None);
323
324            assert!(s.is_terminated());
325        });
326    }
327
328    #[test]
329    fn into_try_stream_transposes_generator_states() {
330        let s = generate(|mut co| async move {
331            co.yield_(1u8).await;
332            co.yield_(2u8).await;
333
334            Result::<(), &'static str>::Err("oops")
335        })
336        .into_try_stream();
337
338        let res = block_on(s.collect::<Vec<Result<u8, &'static str>>>());
339
340        assert_eq!(res, vec![Ok(1), Ok(2), Err("oops")]);
341    }
342
343    #[test]
344    fn into_try_stream_eats_unit_success() {
345        let s = generate(|mut co| async move {
346            co.yield_(1u8).await;
347            co.yield_(2u8).await;
348
349            Result::<(), &'static str>::Ok(())
350        })
351        .into_try_stream();
352
353        let res = block_on(s.collect::<Vec<Result<u8, &'static str>>>());
354
355        assert_eq!(res, vec![Ok(1), Ok(2)]);
356    }
357
358    #[test]
359    fn runs_task_to_completion() {
360        let finished = Counter::default();
361
362        let make_s = || {
363            generate(|mut co| async {
364                co.yield_(8u8).await;
365
366                // Try really hard to cause this task to be dropped without completing.
367                drop(co);
368                yield_once().await;
369
370                finished.inc();
371            })
372        };
373
374        // No matter which combinator is used.
375
376        block_on(async {
377            let res = make_s().collect::<Vec<GeneratorState<u8, ()>>>().await;
378            assert_eq!(res, vec![GeneratorState::Yielded(8), GeneratorState::Complete(())]);
379            assert_eq!(finished.take(), 1);
380        });
381
382        block_on(async {
383            assert_eq!(make_s().into_yielded().collect::<Vec<_>>().await, vec![8]);
384            assert_eq!(finished.take(), 1);
385        });
386
387        block_on(async {
388            let () = make_s().into_complete().await;
389            assert_eq!(finished.take(), 1);
390        });
391    }
392
393    #[test]
394    fn fibonacci() {
395        let fib = generate(|mut co| async move {
396            let (mut a, mut b) = (0u32, 1u32);
397            loop {
398                co.yield_(a).await;
399
400                let n = b;
401                b += a;
402                a = n;
403            }
404        })
405        .into_yielded()
406        .take(10)
407        .collect::<Vec<_>>();
408
409        assert_eq!(block_on(fib), vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34]);
410    }
411}