1use super::Stream;
6use pin_project::pin_project;
7use std::future::Future;
8use std::pin::Pin;
9use std::task::{Context, Poll};
10
11const FOR_EACH_COOPERATIVE_BUDGET: usize = 1024;
15
16#[pin_project]
20#[derive(Debug)]
21#[must_use = "futures do nothing unless polled"]
22pub struct ForEach<S, F> {
23 #[pin]
24 stream: S,
25 f: F,
26 completed: bool,
27}
28
29impl<S, F> ForEach<S, F> {
30 #[inline]
32 pub(crate) fn new(stream: S, f: F) -> Self {
33 Self {
34 stream,
35 f,
36 completed: false,
37 }
38 }
39}
40
41impl<S, F> Future for ForEach<S, F>
42where
43 S: Stream,
44 F: FnMut(S::Item),
45{
46 type Output = ();
47
48 #[inline]
49 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
50 let mut this = self.project();
51 if *this.completed {
52 return Poll::Ready(());
53 }
54 let mut processed_this_poll = 0usize;
55 loop {
56 match this.stream.as_mut().poll_next(cx) {
57 Poll::Ready(Some(item)) => {
58 (this.f)(item);
59 processed_this_poll += 1;
60 if processed_this_poll >= FOR_EACH_COOPERATIVE_BUDGET {
61 cx.waker().wake_by_ref();
62 return Poll::Pending;
63 }
64 }
65 Poll::Ready(None) => {
66 *this.completed = true;
67 return Poll::Ready(());
68 }
69 Poll::Pending => return Poll::Pending,
70 }
71 }
72 }
73}
74
75#[pin_project]
79#[derive(Debug)]
80#[must_use = "futures do nothing unless polled"]
81pub struct ForEachAsync<S, F, Fut> {
82 #[pin]
83 stream: S,
84 f: F,
85 #[pin]
86 pending: Option<Fut>,
87 completed: bool,
88}
89
90impl<S, F, Fut> ForEachAsync<S, F, Fut> {
91 #[inline]
92 pub(crate) fn new(stream: S, f: F) -> Self {
93 Self {
94 stream,
95 f,
96 pending: None,
97 completed: false,
98 }
99 }
100}
101
102impl<S, F, Fut> Future for ForEachAsync<S, F, Fut>
103where
104 S: Stream,
105 F: FnMut(S::Item) -> Fut,
106 Fut: Future<Output = ()>,
107{
108 type Output = ();
109
110 #[inline]
111 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
112 let mut this = self.project();
113 if *this.completed {
114 return Poll::Ready(());
115 }
116 let mut processed_this_poll = 0usize;
117 loop {
118 if let Some(fut) = this.pending.as_mut().as_pin_mut() {
120 match fut.poll(cx) {
121 Poll::Ready(()) => {
122 this.pending.set(None);
123 processed_this_poll += 1;
124 if processed_this_poll >= FOR_EACH_COOPERATIVE_BUDGET {
125 cx.waker().wake_by_ref();
126 return Poll::Pending;
127 }
128 }
129 Poll::Pending => return Poll::Pending,
130 }
131 }
132
133 match this.stream.as_mut().poll_next(cx) {
135 Poll::Ready(Some(item)) => {
136 this.pending.set(Some((this.f)(item)));
137 }
138 Poll::Ready(None) => {
139 *this.completed = true;
140 return Poll::Ready(());
141 }
142 Poll::Pending => return Poll::Pending,
143 }
144 }
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 #![allow(
151 clippy::pedantic,
152 clippy::nursery,
153 clippy::expect_fun_call,
154 clippy::map_unwrap_or,
155 clippy::cast_possible_wrap,
156 clippy::future_not_send
157 )]
158 use super::*;
159 use crate::stream::iter;
160 use std::cell::RefCell;
161 use std::sync::Arc;
162 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
163 use std::task::{Poll, Waker};
164
165 fn noop_waker() -> Waker {
166 std::task::Waker::noop().clone()
167 }
168
169 struct TrackWaker(Arc<AtomicBool>);
170
171 use std::task::Wake;
172 impl Wake for TrackWaker {
173 fn wake(self: Arc<Self>) {
174 self.0.store(true, Ordering::SeqCst);
175 }
176
177 fn wake_by_ref(self: &Arc<Self>) {
178 self.0.store(true, Ordering::SeqCst);
179 }
180 }
181
182 #[derive(Debug, Default)]
183 struct AlwaysReadyCounter {
184 next: usize,
185 end: usize,
186 }
187
188 impl AlwaysReadyCounter {
189 fn new(end: usize) -> Self {
190 Self { next: 0, end }
191 }
192 }
193
194 impl Stream for AlwaysReadyCounter {
195 type Item = usize;
196
197 fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
198 if self.next >= self.end {
199 return Poll::Ready(None);
200 }
201
202 let item = self.next;
203 self.next += 1;
204 Poll::Ready(Some(item))
205 }
206 }
207
208 #[derive(Debug)]
209 struct PollCountingEmptyStream {
210 polls: Arc<AtomicUsize>,
211 }
212
213 impl PollCountingEmptyStream {
214 fn new(polls: Arc<AtomicUsize>) -> Self {
215 Self { polls }
216 }
217 }
218
219 impl Stream for PollCountingEmptyStream {
220 type Item = usize;
221
222 fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
223 self.polls.fetch_add(1, Ordering::SeqCst);
224 Poll::Ready(None)
225 }
226 }
227
228 #[derive(Debug)]
229 struct PendingOnceThenItems<T> {
230 items: Vec<T>,
231 index: usize,
232 pending_first: bool,
233 }
234
235 impl<T> PendingOnceThenItems<T> {
236 fn new(items: Vec<T>) -> Self {
237 Self {
238 items,
239 index: 0,
240 pending_first: true,
241 }
242 }
243 }
244
245 impl<T: Clone + Unpin> Stream for PendingOnceThenItems<T> {
246 type Item = T;
247
248 fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
249 if self.pending_first {
250 self.pending_first = false;
251 return Poll::Pending;
252 }
253 if self.index >= self.items.len() {
254 return Poll::Ready(None);
255 }
256
257 let item = self.items[self.index].clone();
258 self.index += 1;
259 Poll::Ready(Some(item))
260 }
261 }
262
263 fn init_test(name: &str) {
264 crate::test_utils::init_test_logging();
265 crate::test_phase!(name);
266 }
267
268 fn poll_unit_future_to_completion<F>(future: &mut F)
269 where
270 F: std::future::Future<Output = ()> + Unpin,
271 {
272 let waker = noop_waker();
273 let mut cx = Context::from_waker(&waker);
274 loop {
275 match Pin::new(&mut *future).poll(&mut cx) {
276 Poll::Ready(()) => return,
277 Poll::Pending => {}
278 }
279 }
280 }
281
282 fn for_each_side_effects(input: Vec<i32>) -> Vec<i32> {
283 let results = RefCell::new(Vec::new());
284 let mut future = ForEach::new(iter(input), |item| {
285 results.borrow_mut().push(item);
286 });
287 poll_unit_future_to_completion(&mut future);
288 drop(future);
289 results.into_inner()
290 }
291
292 fn for_each_async_side_effects(input: Vec<i32>) -> Vec<i32> {
293 let results = RefCell::new(Vec::new());
294 let mut future = ForEachAsync::new(iter(input), |item| {
295 let results = &results;
296 Box::pin(async move {
297 results.borrow_mut().push(item);
298 })
299 });
300 poll_unit_future_to_completion(&mut future);
301 drop(future);
302 results.into_inner()
303 }
304
305 #[test]
306 fn for_each_collects_side_effects() {
307 init_test("for_each_collects_side_effects");
308 let results = RefCell::new(Vec::new());
309 let mut future = ForEach::new(iter(vec![1i32, 2, 3]), |x| {
310 results.borrow_mut().push(x);
311 });
312 let waker = noop_waker();
313 let mut cx = Context::from_waker(&waker);
314
315 match Pin::new(&mut future).poll(&mut cx) {
316 Poll::Ready(()) => {
317 let collected = results.borrow().clone();
318 let ok = collected == vec![1, 2, 3];
319 crate::assert_with_log!(ok, "collected", vec![1, 2, 3], collected);
320 }
321 Poll::Pending => panic!("expected Ready"),
322 }
323 crate::test_complete!("for_each_collects_side_effects");
324 }
325
326 #[test]
327 fn for_each_empty() {
328 init_test("for_each_empty");
329 let mut called = false;
330 let mut future = ForEach::new(iter(Vec::<i32>::new()), |_| {
331 called = true;
332 });
333 let waker = noop_waker();
334 let mut cx = Context::from_waker(&waker);
335
336 match Pin::new(&mut future).poll(&mut cx) {
337 Poll::Ready(()) => {
338 crate::assert_with_log!(!called, "not called", false, called);
339 }
340 Poll::Pending => panic!("expected Ready"),
341 }
342 crate::test_complete!("for_each_empty");
343 }
344
345 #[test]
346 fn for_each_async() {
347 init_test("for_each_async");
348 let results = RefCell::new(Vec::new());
349 let mut future = ForEachAsync::new(iter(vec![1i32, 2, 3]), |x| {
350 let res = &results;
351 Box::pin(async move {
352 res.borrow_mut().push(x);
353 })
354 });
355 let waker = noop_waker();
356 let mut cx = Context::from_waker(&waker);
357
358 loop {
364 match Pin::new(&mut future).poll(&mut cx) {
365 Poll::Ready(()) => break,
366 Poll::Pending => {} }
368 }
369
370 let collected = results.borrow().clone();
371 let ok = collected == vec![1, 2, 3];
372 crate::assert_with_log!(ok, "collected", vec![1, 2, 3], collected);
373 crate::test_complete!("for_each_async");
374 }
375
376 #[test]
378 fn for_each_async_empty() {
379 init_test("for_each_async_empty");
380 let mut called = false;
381 let mut future = ForEachAsync::new(iter(Vec::<i32>::new()), |_x| {
382 called = true;
383 Box::pin(async {})
384 });
385 let waker = noop_waker();
386 let mut cx = Context::from_waker(&waker);
387
388 let poll = Pin::new(&mut future).poll(&mut cx);
389 let completed = matches!(poll, Poll::Ready(()));
390 crate::assert_with_log!(completed, "async empty completes", true, completed);
391 crate::assert_with_log!(!called, "closure not called", false, called);
392
393 crate::test_complete!("for_each_async_empty");
394 }
395
396 #[test]
397 fn for_each_yields_after_budget_on_always_ready_stream() {
398 init_test("for_each_yields_after_budget_on_always_ready_stream");
399 let seen = RefCell::new(Vec::new());
400 let mut future = ForEach::new(
401 AlwaysReadyCounter::new(FOR_EACH_COOPERATIVE_BUDGET + 5),
402 |x| seen.borrow_mut().push(x),
403 );
404 let woke = Arc::new(AtomicBool::new(false));
405 let waker = Waker::from(Arc::new(TrackWaker(woke.clone())));
406 let mut cx = Context::from_waker(&waker);
407
408 let first = Pin::new(&mut future).poll(&mut cx);
409 crate::assert_with_log!(
410 matches!(first, Poll::Pending),
411 "first poll yields cooperatively",
412 "Poll::Pending",
413 first
414 );
415 crate::assert_with_log!(
416 future.stream.next == FOR_EACH_COOPERATIVE_BUDGET,
417 "upstream advanced only to budget",
418 FOR_EACH_COOPERATIVE_BUDGET,
419 future.stream.next
420 );
421 crate::assert_with_log!(
422 seen.borrow().len() == FOR_EACH_COOPERATIVE_BUDGET,
423 "side effects applied to budget items",
424 FOR_EACH_COOPERATIVE_BUDGET,
425 seen.borrow().len()
426 );
427 crate::assert_with_log!(
428 woke.load(Ordering::SeqCst),
429 "self-wake requested",
430 true,
431 woke.load(Ordering::SeqCst)
432 );
433
434 let second = Pin::new(&mut future).poll(&mut cx);
435 crate::assert_with_log!(
436 matches!(second, Poll::Ready(())),
437 "second poll completes",
438 "Poll::Ready(())",
439 second
440 );
441 crate::assert_with_log!(
442 seen.borrow().len() == FOR_EACH_COOPERATIVE_BUDGET + 5,
443 "all side effects complete",
444 FOR_EACH_COOPERATIVE_BUDGET + 5,
445 seen.borrow().len()
446 );
447 crate::test_complete!("for_each_yields_after_budget_on_always_ready_stream");
448 }
449
450 #[test]
451 fn for_each_async_yields_after_budget_on_immediate_futures() {
452 init_test("for_each_async_yields_after_budget_on_immediate_futures");
453 let seen = RefCell::new(Vec::new());
454 let mut future = ForEachAsync::new(
455 AlwaysReadyCounter::new(FOR_EACH_COOPERATIVE_BUDGET + 5),
456 |x| {
457 let seen = &seen;
458 Box::pin(async move {
459 seen.borrow_mut().push(x);
460 })
461 },
462 );
463 let woke = Arc::new(AtomicBool::new(false));
464 let waker = Waker::from(Arc::new(TrackWaker(woke.clone())));
465 let mut cx = Context::from_waker(&waker);
466
467 let first = Pin::new(&mut future).poll(&mut cx);
468 crate::assert_with_log!(
469 matches!(first, Poll::Pending),
470 "first poll yields cooperatively",
471 "Poll::Pending",
472 first
473 );
474 crate::assert_with_log!(
475 future.stream.next == FOR_EACH_COOPERATIVE_BUDGET,
476 "upstream advanced only to budget",
477 FOR_EACH_COOPERATIVE_BUDGET,
478 future.stream.next
479 );
480 crate::assert_with_log!(
481 future.pending.is_none(),
482 "no pending future left at cooperative boundary",
483 true,
484 future.pending.is_none()
485 );
486 crate::assert_with_log!(
487 seen.borrow().len() == FOR_EACH_COOPERATIVE_BUDGET,
488 "side effects applied to budget items",
489 FOR_EACH_COOPERATIVE_BUDGET,
490 seen.borrow().len()
491 );
492 crate::assert_with_log!(
493 woke.load(Ordering::SeqCst),
494 "self-wake requested",
495 true,
496 woke.load(Ordering::SeqCst)
497 );
498
499 let second = Pin::new(&mut future).poll(&mut cx);
500 crate::assert_with_log!(
501 matches!(second, Poll::Ready(())),
502 "second poll completes",
503 "Poll::Ready(())",
504 second
505 );
506 crate::assert_with_log!(
507 seen.borrow().len() == FOR_EACH_COOPERATIVE_BUDGET + 5,
508 "all side effects complete",
509 FOR_EACH_COOPERATIVE_BUDGET + 5,
510 seen.borrow().len()
511 );
512 crate::test_complete!("for_each_async_yields_after_budget_on_immediate_futures");
513 }
514
515 #[test]
516 fn for_each_repoll_after_completion_fails_closed_without_repolling_upstream() {
517 init_test("for_each_repoll_after_completion_fails_closed_without_repolling_upstream");
518 let polls = Arc::new(AtomicUsize::new(0));
519 let mut future = ForEach::new(PollCountingEmptyStream::new(Arc::clone(&polls)), |_| {});
520 let waker = noop_waker();
521 let mut cx = Context::from_waker(&waker);
522
523 let first = Pin::new(&mut future).poll(&mut cx);
524 crate::assert_with_log!(
525 matches!(first, Poll::Ready(())),
526 "first poll completes",
527 "Poll::Ready(())",
528 first
529 );
530 crate::assert_with_log!(
531 polls.load(Ordering::SeqCst) == 1,
532 "first completion polls upstream once",
533 1,
534 polls.load(Ordering::SeqCst)
535 );
536
537 let repoll = Pin::new(&mut future).poll(&mut cx);
539 crate::assert_with_log!(
540 matches!(repoll, Poll::Ready(())),
541 "repoll returns Ready(())",
542 "Poll::Ready(())",
543 repoll
544 );
545 crate::assert_with_log!(
546 polls.load(Ordering::SeqCst) == 1,
547 "repoll does not touch upstream again",
548 1,
549 polls.load(Ordering::SeqCst)
550 );
551 crate::test_complete!(
552 "for_each_repoll_after_completion_fails_closed_without_repolling_upstream"
553 );
554 }
555
556 #[test]
557 fn for_each_async_repoll_after_completion_fails_closed_without_repolling_upstream() {
558 init_test("for_each_async_repoll_after_completion_fails_closed_without_repolling_upstream");
559 let polls = Arc::new(AtomicUsize::new(0));
560 let mut future =
561 ForEachAsync::new(PollCountingEmptyStream::new(Arc::clone(&polls)), |_| {
562 Box::pin(async {})
563 });
564 let waker = noop_waker();
565 let mut cx = Context::from_waker(&waker);
566
567 let first = Pin::new(&mut future).poll(&mut cx);
568 crate::assert_with_log!(
569 matches!(first, Poll::Ready(())),
570 "first poll completes",
571 "Poll::Ready(())",
572 first
573 );
574 crate::assert_with_log!(
575 polls.load(Ordering::SeqCst) == 1,
576 "first completion polls upstream once",
577 1,
578 polls.load(Ordering::SeqCst)
579 );
580
581 let repoll = Pin::new(&mut future).poll(&mut cx);
583 crate::assert_with_log!(
584 matches!(repoll, Poll::Ready(())),
585 "repoll returns Ready(())",
586 "Poll::Ready(())",
587 repoll
588 );
589 crate::assert_with_log!(
590 polls.load(Ordering::SeqCst) == 1,
591 "repoll does not touch upstream again",
592 1,
593 polls.load(Ordering::SeqCst)
594 );
595 crate::test_complete!(
596 "for_each_async_repoll_after_completion_fails_closed_without_repolling_upstream"
597 );
598 }
599
600 #[test]
601 fn mr_for_each_partitioned_inputs_match_unsplit_side_effects() {
602 init_test("mr_for_each_partitioned_inputs_match_unsplit_side_effects");
603 let input = vec![-3, -1, 0, 1, 2, 5, 8];
604 let expected = for_each_side_effects(input.clone());
605
606 for split in 0..=input.len() {
607 let mut partitioned = for_each_side_effects(input[..split].to_vec());
608 partitioned.extend(for_each_side_effects(input[split..].to_vec()));
609 crate::assert_with_log!(
610 partitioned == expected,
611 format!("split at {split}"),
612 expected.clone(),
613 partitioned
614 );
615 }
616 crate::test_complete!("mr_for_each_partitioned_inputs_match_unsplit_side_effects");
617 }
618
619 #[test]
620 fn mr_for_each_async_immediate_matches_sync_side_effects() {
621 init_test("mr_for_each_async_immediate_matches_sync_side_effects");
622 for input in [
623 Vec::new(),
624 vec![1],
625 vec![1, 1, 2, 3, 5, 8],
626 vec![-10, 0, 10, 20],
627 ] {
628 let sync = for_each_side_effects(input.clone());
629 let async_immediate = for_each_async_side_effects(input.clone());
630 crate::assert_with_log!(
631 async_immediate == sync,
632 format!("input {input:?}"),
633 sync,
634 async_immediate
635 );
636 }
637 crate::test_complete!("mr_for_each_async_immediate_matches_sync_side_effects");
638 }
639
640 #[test]
641 fn mr_for_each_pending_cancellation_before_first_item_has_no_side_effects() {
642 init_test("mr_for_each_pending_cancellation_before_first_item_has_no_side_effects");
643 let input = vec![4, 8, 15, 16, 23, 42];
644 let results = RefCell::new(Vec::new());
645 let mut future = ForEach::new(PendingOnceThenItems::new(input.clone()), |item| {
646 results.borrow_mut().push(item);
647 });
648 let waker = noop_waker();
649 let mut cx = Context::from_waker(&waker);
650
651 let first_poll = Pin::new(&mut future).poll(&mut cx);
652 crate::assert_with_log!(
653 matches!(first_poll, Poll::Pending),
654 "first poll is pending",
655 "Poll::Pending",
656 first_poll
657 );
658 crate::assert_with_log!(
659 results.borrow().is_empty(),
660 "no side effects before first item is ready",
661 true,
662 results.borrow().is_empty()
663 );
664
665 drop(future);
666 let cancelled_effects = results.into_inner();
667 crate::assert_with_log!(
668 cancelled_effects.is_empty(),
669 "cancellation before first ready item has no effects",
670 Vec::<i32>::new(),
671 cancelled_effects
672 );
673
674 let fresh = for_each_side_effects(input.clone());
675 crate::assert_with_log!(
676 fresh == input,
677 "fresh equivalent run still observes all items",
678 input,
679 fresh
680 );
681 crate::test_complete!(
682 "mr_for_each_pending_cancellation_before_first_item_has_no_side_effects"
683 );
684 }
685}