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}