Skip to main content

mpmc_resize/
resize.rs

1use alloc::boxed::Box;
2use core::{marker::PhantomData, ptr::null_mut};
3
4use crossbeam_utils::CachePadded;
5
6use crate::{
7    BoundedCollection,
8    sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering},
9    utils::Backoff,
10};
11
12/// A dynamically resizable wrapper around a [`BoundedCollection`].
13///
14/// This type implements an algorithm that allows any [`BoundedCollection`] to be dynamically resized while preserving some of its properties.
15///
16/// ## Progress Guarantees:
17///
18/// - **Lock Freedom**: if the wrapped collection is lock-free, all corresponding operations on `Resizable` are also lock-free.
19/// - **Obstruction Freedom**: if the wrapped collection exposes obstruction-free methods, all corresponding operations on `Resizable` are also obstruction-free.
20///
21/// `Resizable::resize` is blocking both on allocator and stale readers and writers.
22///
23/// ## Ordering and Consistency Guarantees:
24///
25/// - **Relaxed FIFO**: if the wrapped collection has FIFO ordering, `Resizable` has **k-FIFO** ordering with asymmetric delay and rank error.
26/// - **Linearizability**: if the wrapped collection is linearizable, all operations on `Resizable` are also linearizable with respect to its relaxed FIFO specification.
27/// - **Empty-Linearizability**: if the wrapped collection is linearizale, all operations on [`Resizable`] are empty-linearizable with respect to the wrapped collections specification.
28///
29/// If no call to `resize` happens, or in steady-state, `Resizable` has strict FIFO ordering and is strictly linearizable, given the same holds for the wrapped collection.
30///
31/// For more intormation on rank error and delay consult the crate level documentation.
32#[derive(Debug)]
33pub struct Resizable<Q> {
34    cores: [AtomicPtr<Q>; 2],
35    push_epoch: CachePadded<AtomicUsize>,
36    pop_epoch: CachePadded<AtomicUsize>,
37    active_pushes: CachePadded<[AtomicUsize; 2]>,
38    active_reads: CachePadded<[AtomicUsize; 2]>,
39    is_resizing: AtomicBool,
40    _marker: PhantomData<Box<Q>>,
41}
42
43impl<Q> Resizable<Q> {
44    /// Constructs a new `Resizable` from two raw [`BoundedCollection`] objects.
45    pub fn from_parts(left: Q, right: Q) -> Self {
46        Self {
47            cores: [
48                AtomicPtr::new(Box::into_raw(Box::new(left))),
49                AtomicPtr::new(Box::into_raw(Box::new(right))),
50            ],
51            active_pushes: [AtomicUsize::new(0), AtomicUsize::new(0)].into(),
52            active_reads: [AtomicUsize::new(0), AtomicUsize::new(0)].into(),
53            push_epoch: AtomicUsize::new(0).into(),
54            pop_epoch: AtomicUsize::new(0).into(),
55            is_resizing: AtomicBool::new(false),
56            _marker: PhantomData,
57        }
58    }
59}
60
61impl<Q> Resizable<Q>
62where
63    Q: BoundedCollection,
64{
65    /// Constructs a new `Resizable` with capacity `size`.
66    #[track_caller]
67    pub fn with_capacity(size: usize) -> Resizable<Q> {
68        Self::from_parts(Q::with_capacity(size), Q::with_capacity(1))
69    }
70
71    /// Constructs a new `Resizable`
72    #[track_caller]
73    pub fn new() -> Self {
74        Self::from_parts(Q::with_capacity(1), Q::with_capacity(1))
75    }
76}
77
78impl<Q> Default for Resizable<Q>
79where
80    Q: Default,
81{
82    fn default() -> Self {
83        Self::from_parts(Q::default(), Q::default())
84    }
85}
86
87impl<Q> Drop for Resizable<Q> {
88    fn drop(&mut self) {
89        let left = self.cores[0].swap(null_mut(), Ordering::Acquire);
90        if !left.is_null() {
91            // Safety:
92            // No concurrent drops of this ds can happen.
93            // This queue was allocated in `new` or in `grow_by` with `Box::into_raw` and was not deallocated since then.
94            // we just checked that its non-null
95            _ = unsafe { Box::from_raw(left) };
96        }
97        let right = self.cores[1].swap(null_mut(), Ordering::Acquire);
98        if !right.is_null() {
99            // Safety:
100            // No concurrent drops of this ds can happen.
101            // This queue was allocated in `new` or in `grow_by` with `Box::into_raw` and was not deallocated since then.
102            // we just checked that its non-null
103            _ = unsafe { Box::from_raw(right) };
104        }
105    }
106}
107
108// we need SeqCst on epoch atomics and active_* atomics on the synchronization points,
109// because we have to synchronize between an asymmetric read/write - write/read pattern across the two
110
111impl<Q> Resizable<Q>
112where
113    Q: BoundedCollection,
114{
115    /// Attempts to resize the capacity of the collection to `size` slots.
116    ///
117    /// **Note:** This method may block (on the allocator) or fail spuriously.
118    /// Further a growth event may not be considered finished in regards of an other `resize` being possible until some time after the call to `resize`.
119    ///
120    /// Returns `true` if the resize was successfull, or `false` if
121    /// it failed. Failure can occur due to thread
122    /// contention, incomplete migration of the previous resize, i.e. staleness of the datastructure,
123    /// or other implementation-specific/spurious conditions.
124    pub fn resize(&self, size: usize) -> bool {
125        if size == 0 {
126            return false;
127        }
128        // loom only models `SeqCst` fences correctly, other accesses are degraded.
129        #[cfg(loom)]
130        crate::sync::atomic::fence(Ordering::SeqCst);
131        let push_epoch = self.push_epoch.load(Ordering::SeqCst);
132        let pop_epoch = self.pop_epoch.load(Ordering::SeqCst);
133
134        if pop_epoch != push_epoch {
135            return false;
136        }
137
138        let old_idx = (push_epoch + 1) % 2;
139
140        // wait on any stale readers of the old queue.
141        // Note that at any point during AND after this check other threads may still register as NEW readers on the OLD queue;
142        // This is safe, because they will revalidate the epoch after registration and NOT actually read the underlying queue.
143        // However this means that we may spuriously fail for longer than strictly necessary to ensure noone is actually reading the old queue.
144        #[cfg(loom)]
145        crate::sync::atomic::fence(Ordering::SeqCst);
146        if self.active_reads[old_idx].load(Ordering::SeqCst) != 0
147            || self.active_pushes[old_idx].load(Ordering::SeqCst) != 0
148        {
149            return false;
150        }
151
152        if self.is_resizing.swap(true, Ordering::AcqRel) {
153            return false;
154        }
155
156        #[cfg(loom)]
157        crate::sync::atomic::fence(Ordering::SeqCst);
158        if self.push_epoch.load(Ordering::SeqCst) != push_epoch {
159            // could happen if an entire resize happens between load and this check
160            self.is_resizing.store(false, Ordering::Release);
161            return false;
162        }
163
164        // at this point we know that
165        // a) no concurrent resize is happening
166        // b) since pop_epoch == push_epoch the old queue is empty.
167        // c) since (b) and active_* were both 0 at some point t, all conccurrent ops that are registered on active_*[old_idx] will now revalidate their epoch before accessing the queue
168
169        let new_queue = Box::into_raw(Box::new(Q::with_capacity(size)));
170
171        // Safety:
172        // since pop_epoch == push_epoch all concurrent threads acces the queue at push_epoch % 2.
173        // pop ensures that no pushes are in flight to the old queue anymore and that it is empty. We can safely drop it.
174        let old_queue = self.cores[old_idx].swap(new_queue, Ordering::AcqRel);
175
176        self.push_epoch.fetch_add(1, Ordering::Release);
177
178        // Safety:
179        // old_queue was ocnstucted from a Box::into_raw and is dropped only once, as ensured by epoch guards
180        let q = unsafe { Box::from_raw(old_queue) };
181
182        #[cfg(not(any(loom, shuttle)))]
183        debug_assert!(q.try_pop().is_none());
184        #[cfg(any(loom, shuttle))]
185        assert!(q.try_pop().is_none());
186
187        self.is_resizing.store(false, Ordering::Release);
188        true
189    }
190}
191
192impl<Q> Resizable<Q> {
193    fn get_queue(&self, epoch: usize) -> &Q {
194        let queue = self.cores[epoch % 2].load(Ordering::Acquire);
195        // Safety:
196        // It is guranteed by `resize` that no concurrent mutable access can happen to any queue in cores.
197        // It is safe to access it concurrently via shared ref, as long as queue core is Sync.
198        unsafe { &*queue }
199    }
200
201    fn register_push(&self, target_epoch: usize) -> bool {
202        #[cfg(loom)]
203        crate::sync::atomic::fence(Ordering::SeqCst);
204        self.active_reads[target_epoch % 2].fetch_add(1, Ordering::SeqCst);
205
206        #[cfg(loom)]
207        crate::sync::atomic::fence(Ordering::SeqCst);
208        let current_push = self.push_epoch.load(Ordering::SeqCst);
209
210        // It is safe to read if the target epoch is still structurally active
211        if target_epoch != current_push {
212            self.deregister_reader(target_epoch);
213            return false;
214        }
215        true
216    }
217
218    fn register_pop(&self, target_epoch: usize) -> bool {
219        #[cfg(loom)]
220        crate::sync::atomic::fence(Ordering::SeqCst);
221        self.active_reads[target_epoch % 2].fetch_add(1, Ordering::SeqCst);
222
223        #[cfg(loom)]
224        crate::sync::atomic::fence(Ordering::SeqCst);
225        let current_pop = self.pop_epoch.load(Ordering::SeqCst);
226
227        // It is safe to read if the target epoch is still structurally active
228        if target_epoch != current_pop {
229            self.deregister_reader(target_epoch);
230            return false;
231        }
232        true
233    }
234
235    fn deregister_reader(&self, epoch: usize) {
236        self.active_reads[epoch % 2].fetch_sub(1, Ordering::Release);
237    }
238}
239
240impl<Q> Resizable<Q>
241where
242    Q: BoundedCollection,
243{
244    fn try_pop_from(
245        &self,
246        epoch: usize,
247        registration: impl Fn(&Self, usize) -> bool,
248    ) -> Result<Option<Q::Item>, ()> {
249        if !registration(self, epoch) {
250            return Err(());
251        }
252
253        let item = self.get_queue(epoch).try_pop();
254
255        self.deregister_reader(epoch);
256
257        Ok(item)
258    }
259}
260
261impl<Q> BoundedCollection for Resizable<Q>
262where
263    Q: BoundedCollection,
264{
265    type Item = Q::Item;
266
267    fn try_push(&self, item: Self::Item) -> Result<(), Self::Item> {
268        let mut backoff = Backoff::new();
269        loop {
270            let push_epoch = self.push_epoch.load(Ordering::Acquire);
271            self.active_pushes[push_epoch % 2].fetch_add(1, Ordering::SeqCst);
272
273            #[cfg(loom)]
274            crate::sync::atomic::fence(Ordering::SeqCst);
275            if self.push_epoch.load(Ordering::SeqCst) == push_epoch {
276                let r = self.get_queue(push_epoch).try_push(item);
277
278                self.active_pushes[push_epoch % 2].fetch_sub(1, Ordering::Release);
279                return r;
280            }
281            self.active_pushes[push_epoch % 2].fetch_sub(1, Ordering::Release);
282            backoff.backoff();
283        }
284    }
285
286    fn try_pop(&self) -> Option<Self::Item> {
287        #[cfg(any(shuttle, loom))]
288        let mut backoff = Backoff::new();
289
290        // if push_epoch != pop_epoch, we need to drain the old queue.
291        // In order to provide `empty-linearizability` we do a double collect over BOTH queues.
292        //
293        // If push_epoch == pop_epoch we only need to do another sweep IF push_epoch has changed by the end of this call
294        for _ in 0..2 {
295            loop {
296                let push_epoch = self.push_epoch.load(Ordering::Acquire);
297                let pop_epoch = self.pop_epoch.load(Ordering::Acquire);
298
299                if pop_epoch != push_epoch {
300                    // drain old buffer
301
302                    // it is safe to call get_queue on pop_epoch here, since no resize can happen while we have not updated pop_epoch and reads on this epoch are happening
303                    let Ok(item) = self.try_pop_from(pop_epoch, Self::register_pop) else {
304                        #[cfg(any(shuttle, loom))]
305                        backoff.backoff();
306                        continue;
307                    };
308
309                    if item.is_some() {
310                        return item;
311                    }
312
313                    if self.active_pushes[pop_epoch % 2].load(Ordering::Acquire) == 0 {
314                        let Ok(item) = self.try_pop_from(pop_epoch, Self::register_pop) else {
315                            #[cfg(any(shuttle, loom))]
316                            backoff.backoff();
317                            continue;
318                        };
319
320                        if item.is_some() {
321                            return item;
322                        }
323
324                        #[cfg(loom)]
325                        crate::sync::atomic::fence(Ordering::SeqCst);
326                        _ = self.pop_epoch.compare_exchange_weak(
327                            pop_epoch,
328                            pop_epoch + 1,
329                            Ordering::SeqCst,
330                            Ordering::Relaxed,
331                        );
332
333                        #[cfg(any(shuttle, loom))]
334                        backoff.backoff();
335                        continue;
336                    }
337
338                    // at this point the old queue did not contain any items, even though items are in-flight. At this point the new container may already contain items.
339                    // We face a tradeoff:
340                    //
341                    // a) continue in the inner loop and block on the active_pushers -> violates non-blocking/obstruction-freedom guarantees
342                    // b) check the new container -> opens up the possibilty for item reordering, even in spsc scenarios, i.e. violates FIFO guarantees + linearizability
343                    // It is worth noting here that the extend of reordering per item (i.e. the rank error) is bounded exactly by the number of threads concurrently executing `push`
344                    // and the number of items reordered (i.e. the delay) is bounded by exactly those threads calling `pop` in this scenario.
345                    // In practice the rank error and delay are much lower, because a specific schedule is reuqired for a reordering to happen.
346                    // c) bail, even though the container is non-empty -> violates linearizability (and emptiness assumptions) in that case.
347                    // Even worse: if some active_pusher is indefinitely dead, we will henceforth only bail. Thus this option implicitly blocks on the stalled pusher.
348                    //
349                    // c is of course unaccaptable.
350                    //
351                    // This would be circumventeable iff a helping mechanims where added or the old container were inactivated,
352                    // both of which is not possible given the opaque inner container type and without large changes to the algorithm.
353                    //
354                    // the fundamental question is:
355                    // Do we want complete lock-freedom in `push` and `pop`, or do we want strict 0-FIFO ordering + linearizability always.
356                    // This tradeoff may fall differently in different contexts, however it seems reasonable to relax strict FIFO guarantees and linearizability
357                    // and accept N-FIFO semantics while resizing the queue. N-FIFO semantics should in practice keep most of the benefits of 0-FIFO, while still preserving lock-freedom.
358                    // Note that we do still preserve `empty-linearizability` here by double collecting:
359                    // if the first iteration turns out to be double None, we have two possibilities:
360                    // a) the old queue was truly empty at the point of popping from the new queue
361                    // b) the old queue was non-empty at that point
362                    // if the second iteration returns an item, this doesnt matter
363                    // if it returns None also (for the old queue), then we have know that a was correct OR the item was popped by someone else, in which case we are also safe.
364                }
365
366                let Ok(item) = self.try_pop_from(push_epoch, Self::register_push) else {
367                    #[cfg(any(shuttle, loom))]
368                    backoff.backoff();
369                    continue;
370                };
371
372                if item.is_none() && push_epoch != self.push_epoch.load(Ordering::Acquire) {
373                    #[cfg(any(shuttle, loom))]
374                    backoff.backoff();
375                    continue;
376                }
377
378                if item.is_some() || push_epoch == pop_epoch {
379                    return item;
380                }
381
382                // else do the second collect pass if we come from the slow path
383                #[cfg(any(loom, shuttle))]
384                backoff.backoff();
385                break;
386            }
387        }
388
389        // there was a linearizable time point during the iteration where both collections where truly empty
390        None
391    }
392
393    fn capacity(&self) -> usize {
394        // the capacity of the currently active collection, i.e. the number of elements that can be pushed directly after resize
395        loop {
396            let push_epoch = self.push_epoch.load(Ordering::Acquire);
397            if !self.register_push(push_epoch) {
398                continue;
399            }
400            let cap = self.get_queue(push_epoch).capacity();
401            self.deregister_reader(push_epoch);
402            return cap;
403        }
404    }
405
406    fn len(&self) -> usize {
407        // the total elements in the collections. Note that len can be > capacity.
408        loop {
409            let push_epoch = self.push_epoch.load(Ordering::Acquire);
410            if !self.register_push(push_epoch) {
411                continue;
412            }
413
414            let pop_epoch = self.pop_epoch.load(Ordering::Acquire);
415            let pop_len = if pop_epoch != push_epoch {
416                if !self.register_pop(pop_epoch) {
417                    self.deregister_reader(push_epoch);
418                    continue;
419                }
420
421                let pop_len = self.get_queue(pop_epoch).len();
422                self.deregister_reader(pop_epoch);
423                pop_len
424            } else {
425                0
426            };
427
428            let len = self.get_queue(push_epoch).len() + pop_len;
429            self.deregister_reader(push_epoch);
430            return len;
431        }
432    }
433
434    fn is_empty(&self) -> bool {
435        // the collection is empty if pop() returns None
436        loop {
437            let push_epoch = self.push_epoch.load(Ordering::Acquire);
438            if !self.register_push(push_epoch) {
439                continue;
440            }
441
442            let pop_epoch = self.pop_epoch.load(Ordering::Acquire);
443            let pop_is_empty = if pop_epoch != push_epoch {
444                if !self.register_pop(pop_epoch) {
445                    self.deregister_reader(push_epoch);
446                    continue;
447                }
448
449                let pop_is_empty = self.get_queue(pop_epoch).is_empty();
450                self.deregister_reader(pop_epoch);
451                pop_is_empty
452            } else {
453                true
454            };
455
456            let is_empty = self.get_queue(push_epoch).is_empty() && pop_is_empty;
457            self.deregister_reader(push_epoch);
458            return is_empty;
459        }
460    }
461
462    fn is_full(&self) -> bool {
463        // the collection is full if push() fails
464        loop {
465            let push_epoch = self.push_epoch.load(Ordering::Acquire);
466            if !self.register_push(push_epoch) {
467                continue;
468            }
469            let is_full = self.get_queue(push_epoch).is_full();
470            self.deregister_reader(push_epoch);
471
472            return is_full;
473        }
474    }
475
476    fn with_capacity(capacity: usize) -> Self {
477        Resizable::with_capacity(capacity)
478    }
479}
480
481// convenience methods
482
483#[cfg(not(any(loom, shuttle, echeneis)))]
484impl<Q> Resizable<Q> {
485    /// Deconstructs a `Resizable` into its components.
486    ///
487    /// Returns the left and right raw collections currently used by this object.
488    pub fn into_parts(self) -> [Box<Q>; 2] {
489        let left = self.cores[0].swap(null_mut(), Ordering::Acquire);
490        // Safety:
491        // No concurrent owners of this can happen.
492        // This collection was allocated in `new` or in `grow_by` with `Box::into_raw` and was not deallocated since then.
493        // the collection will be dropped after this.
494        let left = unsafe { Box::from_raw(left) };
495
496        let right = self.cores[1].swap(null_mut(), Ordering::Acquire);
497        // Safety:
498        // No concurrent owners of this can happen.
499        // This collection was allocated in `new` or in `grow_by` with `Box::into_raw` and was not deallocated since then.
500        // the collection will be dropped after this.
501        let right = unsafe { Box::from_raw(right) };
502
503        [left, right]
504    }
505
506    /// returns mutable references to both wrapped collections.
507    pub fn parts_mut(&mut self) -> [&mut Q; 2] {
508        // Safety:
509        // We are the only one accessing the wrapped collections.
510        // No need for synchronization.
511        // left and right can only be null during Drop.
512        let left = unsafe { &mut **self.cores[0].get_mut() };
513        // Safety:
514        // We are the only one accessing the wrapped collections.
515        // No need for synchronization.
516        // left and right can only be null during Drop.
517        let right = unsafe { &mut **self.cores[1].get_mut() };
518
519        [left, right]
520    }
521
522    /// Deconstructs a `Resizable` into its currently active component.
523    ///
524    /// Returns the raw collection currently used by this object.
525    pub fn into_current(mut self) -> Box<Q> {
526        let push_epoch = *self.push_epoch.get_mut();
527        let parts = self.into_parts();
528        parts.into_iter().nth(push_epoch % 2).unwrap()
529    }
530
531    /// Returns a mutable reference to the currently active raw collection.
532    pub fn current_mut(&mut self) -> &mut Q {
533        // Safety:
534        // We are the only one accessing the wrapped collections.
535        // No need for synchronization.
536        // left and right can only be null during Drop.
537        unsafe { &mut **self.cores[*self.push_epoch.get_mut() % 2].get_mut() }
538    }
539}
540
541#[cfg(not(any(loom, shuttle, echeneis)))]
542impl<Q> Resizable<Q>
543where
544    Q: BoundedCollection,
545{
546    /// Attempts to pop an item from the collection.
547    ///
548    /// This method cicumvents the logic synchronization of `Resizable::try_pop`.
549    pub fn pop_mut(&mut self) -> Option<Q::Item> {
550        let pop_idx = *self.pop_epoch.get_mut() % 2;
551        let push_idx = *self.push_epoch.get_mut() % 2;
552
553        if pop_idx == push_idx {
554            self.current_mut().try_pop()
555        } else {
556            let parts = self.parts_mut();
557            parts[pop_idx].try_pop().or(parts[push_idx].try_pop())
558        }
559    }
560
561    /// Attmepts to push an item into the collection.
562    ///
563    /// This method cicumvents the synchronization logic of `Resizable::try_push`.
564    pub fn push_mut(&mut self, item: Q::Item) -> Result<(), Q::Item> {
565        self.current_mut().try_push(item)
566    }
567
568    /// Clears the collection.
569    pub fn clear(&mut self) {
570        for core in self.parts_mut() {
571            while core.try_pop().is_some() {}
572        }
573    }
574
575    /// Migrates all remaining stale items in the old queue into the currently active queue, while ensuring enough capacity.
576    ///
577    /// A subsequent call to [`Self::resize`] will succeed.
578    pub fn migrate(&mut self) {
579        let pop_epoch = *self.pop_epoch.get_mut();
580        let push_epoch = *self.push_epoch.get_mut();
581
582        if pop_epoch == push_epoch {
583            return;
584        }
585
586        let parts = self.parts_mut();
587        let pop_queue = &parts[pop_epoch % 2];
588        let push_queue = &parts[push_epoch % 2];
589
590        let total_items = pop_queue.len() + push_queue.len();
591        let empty_collection = Q::with_capacity(total_items);
592
593        while let Some(item) = pop_queue.try_pop() {
594            _ = empty_collection.try_push(item);
595        }
596
597        while let Some(item) = push_queue.try_pop() {
598            _ = empty_collection.try_push(item);
599        }
600
601        let new_queue = Box::into_raw(Box::new(empty_collection));
602
603        // Safety:
604        // since pop_epoch == push_epoch all concurrent threads access the queue at push_epoch % 2.
605        // pop ensures that no pushes are in flight to the old queue anymore and that it is empty. We can safely drop it.
606        let old_queue = self.cores[pop_epoch % 2].swap(new_queue, Ordering::Relaxed);
607
608        self.push_epoch.fetch_add(1, Ordering::Relaxed);
609        self.pop_epoch.store(push_epoch + 1, Ordering::Relaxed);
610
611        // Safety:
612        // old_queue was consrtucted from a Box::into_raw and is dropped only once, as ensured by epoch guards
613        let _q = unsafe { Box::from_raw(old_queue) };
614    }
615
616    /// Drains all remaining items in the collection.
617    /// Returns an iterator over the drained items.
618    ///
619    /// All remaining items are dropped if the [`Drain`] is dropped.
620    pub fn drain(&mut self) -> Drain<'_, Q, impl Iterator<Item = &mut Q>> {
621        let cores = self.parts_mut();
622
623        Drain {
624            cores: cores.into_iter(),
625            next_up: None,
626        }
627    }
628}
629
630#[cfg(not(any(loom, shuttle, echeneis)))]
631impl<Q: BoundedCollection> Extend<Q::Item> for Resizable<Q> {
632    fn extend<I: IntoIterator<Item = Q::Item>>(&mut self, iter: I) {
633        for item in iter {
634            if let Err(item) = self.push_mut(item) {
635                // we ran out of space
636                // ensure that we can resize
637                self.migrate();
638                let cap = self.capacity();
639                // make more space
640                debug_assert!(
641                    self.resize(cap * 2),
642                    "resize is promised to succeed after a call to migrate"
643                );
644                _ = self.push_mut(item);
645            }
646        }
647    }
648}
649
650/// An iterator that drains all remaining items from multiple collections.
651///
652/// All remaining items are dropped on [`Drain::drop`].
653pub struct Drain<'a, Q: BoundedCollection, I: Iterator<Item = &'a mut Q>> {
654    cores: I,
655    next_up: Option<&'a mut Q>,
656}
657
658impl<'a, Q: BoundedCollection, I: Iterator<Item = &'a mut Q>> Iterator for Drain<'a, Q, I> {
659    type Item = Q::Item;
660
661    fn next(&mut self) -> Option<Self::Item> {
662        if let Some(current) = &mut self.next_up {
663            if let Some(item) = current.try_pop() {
664                return Some(item);
665            }
666        }
667
668        for next in self.cores.by_ref() {
669            if let Some(item) = next.try_pop() {
670                self.next_up.replace(next);
671                return Some(item);
672            }
673        }
674
675        None
676    }
677}
678
679impl<'a, Q: BoundedCollection, I: Iterator<Item = &'a mut Q>> Drop for Drain<'a, Q, I> {
680    fn drop(&mut self) {
681        for _ in self {}
682    }
683}
684
685/// An iterator over a [`Resizable`]
686pub struct IntoIter<Q: BoundedCollection> {
687    old: Option<Box<Q>>,
688    new: Option<Box<Q>>,
689}
690
691impl<Q: BoundedCollection> Iterator for IntoIter<Q> {
692    type Item = Q::Item;
693
694    fn next(&mut self) -> Option<Self::Item> {
695        if let Some(old_q) = &mut self.old {
696            if let Some(item) = old_q.try_pop() {
697                return Some(item);
698            }
699            self.old = None;
700        }
701
702        self.new.as_mut().and_then(|q| q.try_pop())
703    }
704}
705
706#[cfg(not(any(loom, shuttle, echeneis)))]
707impl<Q: BoundedCollection> IntoIterator for Resizable<Q> {
708    type IntoIter = IntoIter<Q>;
709    type Item = Q::Item;
710
711    fn into_iter(mut self) -> Self::IntoIter {
712        let push_epoch = *self.push_epoch.get_mut();
713        let [left, right] = self.into_parts();
714
715        #[allow(clippy::manual_is_multiple_of)]
716        let (old, new) = if push_epoch % 2 == 0 {
717            (right, left)
718        } else {
719            (left, right)
720        };
721
722        IntoIter {
723            old: Some(old),
724            new: Some(new),
725        }
726    }
727}