Skip to content

Commit cff6c4c

Browse files
wfrasertaiki-e
authored andcommitted
FuturesUnordered: fix partial iteration (#2574)
The IntoIter impl advances the head pointer every iteration, but this breaks the linked list invariant that the head's prev should be null. If the iteration is not done to completion, on subsequent drop, FuturesUnordered::unlink relies on this broken invariant and ends up panicking. The fix is to maintain the `head->prev == null` invariant while iterating. Also added a test for this bug.
1 parent a619ea7 commit cff6c4c

File tree

2 files changed

+18
-0
lines changed

2 files changed

+18
-0
lines changed

futures-util/src/stream/futures_unordered/iter.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use super::task::Task;
22
use super::FuturesUnordered;
33
use core::marker::PhantomData;
44
use core::pin::Pin;
5+
use core::ptr;
56
use core::sync::atomic::Ordering::Relaxed;
67

78
/// Mutable iterator over all futures in the unordered set.
@@ -58,6 +59,9 @@ impl<Fut: Unpin> Iterator for IntoIter<Fut> {
5859
// valid `next_all` checks can be skipped.
5960
let next = (**task).next_all.load(Relaxed);
6061
*task = next;
62+
if !task.is_null() {
63+
*(**task).prev_all.get() = ptr::null_mut();
64+
}
6165
self.len -= 1;
6266
Some(future)
6367
}

futures/tests/stream_futures_unordered.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,20 @@ fn into_iter_len() {
261261
assert!(into_iter.next().is_none());
262262
}
263263

264+
#[test]
265+
fn into_iter_partial() {
266+
let stream = vec![future::ready(1), future::ready(2), future::ready(3), future::ready(4)]
267+
.into_iter()
268+
.collect::<FuturesUnordered<_>>();
269+
270+
let mut into_iter = stream.into_iter();
271+
assert!(into_iter.next().is_some());
272+
assert!(into_iter.next().is_some());
273+
assert!(into_iter.next().is_some());
274+
assert_eq!(into_iter.len(), 1);
275+
// don't panic when iterator is dropped before completing
276+
}
277+
264278
#[test]
265279
fn futures_not_moved_after_poll() {
266280
// Future that will be ready after being polled twice,

0 commit comments

Comments
 (0)