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