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