Skip to main content

rama_utils/collections/
append_only_vec.rs

1#![expect(
2    clippy::panic,
3    clippy::multiple_unsafe_ops_per_block,
4    clippy::allow_attributes,
5    reason = "vendored from upstream `append-only-vec`; matches stdlib panicking conventions and preserves upstream idioms"
6)]
7
8use crate::std::alloc::handle_alloc_error;
9
10use core::{mem::ManuallyDrop, ptr};
11
12#[cfg(not(all(loom, test)))]
13use crate::std::alloc::{Layout, alloc, dealloc};
14#[cfg(not(all(loom, test)))]
15use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
16
17#[cfg(all(loom, test))]
18use loom::{
19    alloc::{Layout, alloc, dealloc},
20    sync::atomic::{AtomicPtr, AtomicUsize, Ordering},
21};
22
23#[derive(Debug)]
24/// Append only vec of items `T`.
25///
26/// This vec will never re-allocate and never remove items. This means
27/// that as long as this vec is around, we can have valid references to
28/// all the data it stores. This also means that we can add items to the
29/// vec without having a mutable reference to it.
30///
31/// This vec has a fixed maximum capacity as configured by the const generic
32/// parameters. Calling [`Self::push`] after that capacity is exhausted will panic.
33///
34///
35/// AMOUNT_OF_BINS is total amount of item bins (=arrays). Each bin has double
36/// the capacity then the one before, so even with a low number here,
37/// we should be able to store a huge amount of items.
38///
39/// BIN_OFFSET calculates the offset of the first bin. Effectively this mean our
40/// first bin will have 2^BIN_OFFSET size.
41pub struct AppendOnlyVec<T, const AMOUNT_OF_BINS: usize = 32, const BIN_OFFSET: u32 = 3> {
42    /// Amount of items actually stored in this vec (this is updated when value is stored)
43    count: AtomicUsize,
44    /// Amount of items reserved in this vec (this is updated immediately on insert)
45    reserved: AtomicUsize,
46
47    data: [AtomicPtr<T>; AMOUNT_OF_BINS],
48}
49
50impl<T, const AMOUNT_OF_BINS: usize, const BIN_OFFSET: u32>
51    AppendOnlyVec<T, AMOUNT_OF_BINS, BIN_OFFSET>
52{
53    const INITIAL_BIN_SIZE: usize = (2_usize).pow(BIN_OFFSET);
54
55    /// Create a new [`AppendOnlyVec`] of `T` items
56    ///
57    /// ```compile_fail
58    /// use rama_utils::collections::AppendOnlyVec;
59    /// // This should fail because this overflow INITIAL_BIN_SIZE
60    /// _ = AppendOnlyVec::<usize, 300, 100>::new();
61    /// ```
62    ///
63    /// ```compile_fail
64    /// use rama_utils::collections::AppendOnlyVec;
65    /// // This should fail because the total size exceeds isize::MAX
66    /// _ = AppendOnlyVec::<u64, 60, 10>::new();
67    /// ```
68    pub fn new() -> Self {
69        // This has as a side effect that it will check if capacity fits in usize and is not 0
70        const {
71            if Self::capacity() == 0 {
72                panic!("append only vec does not support 0 capacity")
73            }
74        };
75        // This has as a side effect that it will check if array layout T is not too big
76        const { Self::assert_layout() }
77
78        Self {
79            count: AtomicUsize::new(0),
80            reserved: AtomicUsize::new(0),
81            data: core::array::from_fn(|_| AtomicPtr::new(core::ptr::null_mut())),
82        }
83    }
84
85    /// Pushes an element and returns its index.
86    ///
87    /// # Panics
88    ///
89    /// Panics when the fixed maximum capacity is exceeded.
90    /// Use [`Self::capacity`] to inspect the configured limit.
91    pub fn push(&self, element: T) -> usize {
92        let idx = self.reserved.fetch_add(1, Ordering::Relaxed);
93        if idx >= Self::capacity() {
94            panic!("append only vec has exceeded max capacity")
95        }
96
97        let (bin_idx, offset) = Self::indices(idx);
98
99        // Only allocate a bin if offset = 0. This means there will only ever be one thread
100        // that allocates a buffer.
101
102        // Note that create_bin_if_needed supports cooperative allocation, so in case we ever
103        // which to use that, we just need to always call create_bin_if_needed regardless of offset.
104        // Also note that we do already use the cooperative logic in case reserve() is used.
105
106        // The pros and cons of either approach are:
107        // Not cooperative: single allocation, but other threads need to use spin_wait until this allocation is finished
108        // Cooperative: potential of many allocations for the same bin (dropped after, so only short spike), who-ever is fastest wins
109
110        // One first sight cooperative seems nicer, but also note that our idx and length logic is sequential, so even
111        // if we make the allocation cooperative and not blocking, we will mostly be moving the spin_wait to that step.
112        // For our use case we also don't expect many (if any) concurrent/parallel pushes to this vec.
113
114        let bucket_ptr = if offset == 0 {
115            self.create_bin_if_needed(bin_idx)
116        } else {
117            let mut failures = 0;
118            let mut ptr = self.data[bin_idx].load(Ordering::Acquire);
119            while ptr.is_null() {
120                spin_wait(&mut failures);
121                ptr = self.data[bin_idx].load(Ordering::Acquire);
122            }
123            ptr
124        };
125
126        // Safety:
127        // - Offset fits in ISize::Max, guaranteed by our layout check
128        // - create_bin_if_needed has allocated a continous buffer that is big enough for this
129        unsafe {
130            bucket_ptr.add(offset).write(element);
131        }
132
133        let mut failures = 0;
134        while self
135            .count
136            .compare_exchange(idx, idx + 1, Ordering::Release, Ordering::Relaxed)
137            .is_err()
138        {
139            spin_wait(&mut failures);
140        }
141
142        idx
143    }
144
145    // NOTE: right now we don't support reserve since it's actually quite complex to implement,
146    // and there are many different ways of doing if for a datastructure like this which has
147    // shared push() access. This is fine for our use case since this AppendOnlyVec never
148    // re-allocates. If we ever need reserve() in the future it's definetely possible to add it,
149    // but for now I prefer a simple (and hopefully bugfree) datastructure.
150
151    // Eg some questions for reserve:
152    // - Does reserve() allocate slots for the caller only, or is this best effort
153    // - Does it return a size hint of what was reserved, if so what hint
154    // - Does calling it multiple times reserve new blocks, or do we consider not used blocks also.
155    //   In case we need to support calling this multiple times we will need another Atomic to track this.
156    // - Is reserving cooperative with push(), or do we need synchronisation between the two
157
158    // pub fn reserve(&self, amount: usize) -> usize {}
159
160    pub fn get(&self, idx: usize) -> Option<&T> {
161        if idx >= self.len() {
162            return None;
163        }
164        // Safety: this is safe because we check if idx is within bounds
165        unsafe { Some(self.get_unchecked(idx)) }
166    }
167
168    pub fn is_empty(&self) -> bool {
169        self.count.load(Ordering::Acquire) == 0
170    }
171
172    pub fn len(&self) -> usize {
173        self.count.load(Ordering::Acquire)
174    }
175
176    /// Returns the maximum number of elements this configuration can hold.
177    /// Total capacity = initial_bin_size * (2^AMOUNT_OF_BINS - 1)
178    pub const fn capacity() -> usize {
179        Self::INITIAL_BIN_SIZE * ((1 << AMOUNT_OF_BINS) - 1)
180    }
181
182    /// Returns an iterator over the elements currently in the vector.
183    /// The iterator snapshots the length at creation time.
184    pub fn iter(&self) -> Iter<'_, T, AMOUNT_OF_BINS, BIN_OFFSET> {
185        Iter {
186            vec: self,
187            start: 0,
188            end: self.len(),
189        }
190    }
191
192    /// Returns a pointer to the bin with bin_idx. If this bin does not exist it will be created.
193    ///
194    /// Note: this functions supports cooperative allocations. Meaning it can be called
195    /// in parallel/concurrently. In that case the first one to update `self.data[bin_idx]` will win.
196    /// The slower ones will de-allocate and use that pointer instead.
197    fn create_bin_if_needed(&self, bin_idx: usize) -> *mut T {
198        let mut ptr = self.data[bin_idx].load(Ordering::Acquire);
199        if ptr.is_null() {
200            // Make sure we support zero sized traits
201            let (layout, new_ptr) = if core::mem::size_of::<T>() == 0 {
202                (None, core::ptr::NonNull::<T>::dangling().as_ptr())
203            } else {
204                #[allow(
205                    clippy::expect_used,
206                    reason = "constructor has checked this on creation"
207                )]
208                let layout = Layout::array::<T>(Self::bin_size(bin_idx))
209                    .expect("layout of array T with size");
210
211                // Safety:
212                // - We check that our type is not a zero sized one
213                // - We checked layout in constructor
214                let ptr = unsafe { alloc(layout) as *mut T };
215                if ptr.is_null() {
216                    handle_alloc_error(layout);
217                }
218                (Some(layout), ptr)
219            };
220
221            match self.data[bin_idx].compare_exchange(
222                ptr::null_mut(),
223                new_ptr,
224                Ordering::Release,
225                Ordering::Acquire,
226            ) {
227                Ok(_) => ptr = new_ptr,
228                // If another thread already updated data[bin_idx], use that bin, and de-allocate
229                // the bin we just allocated
230                Err(found) => {
231                    if let Some(layout) = layout {
232                        // Safety:
233                        // - We just allocated this ptr so it exists
234                        // - Layout matches the exact layout of creation
235                        unsafe { dealloc(new_ptr as *mut u8, layout) };
236                    }
237                    ptr = found;
238                }
239            }
240        }
241        ptr
242    }
243
244    /// Calculate the position in our data structure
245    ///
246    /// Returns (bin_index, offset_in_this_bin)
247    const fn indices(i: usize) -> (usize, usize) {
248        // offset this so we are alligned for ilog2
249        let i = i + Self::INITIAL_BIN_SIZE;
250
251        // remove the offset so we start counting bins from 0
252        let bin = (i.ilog2() - BIN_OFFSET) as usize;
253
254        // substract bin_size to find where in this bin we should be
255        let offset = i - Self::bin_size(bin);
256        (bin, offset)
257    }
258
259    /// Get the size of a bin.
260    ///
261    /// We start with INITIAL_BIN_SIZE slots and then we always double the storage
262    /// capacity (alwasy double = bitshift)
263    const fn bin_size(idx: usize) -> usize {
264        Self::INITIAL_BIN_SIZE << idx
265    }
266
267    /// Get item with idx from this vec
268    ///
269    /// # Safety
270    /// This function is safe if idx < self.len()
271    pub unsafe fn get_unchecked(&self, idx: usize) -> &T {
272        let (bin_idx, offset) = Self::indices(idx);
273        let bucket = self.data[bin_idx].load(Ordering::Acquire);
274
275        // Safety: this is safe if idx < self.len()
276        unsafe { &*bucket.add(offset) }
277    }
278
279    /// This function will make sure at compile time that our parameters are not
280    /// too big. This will make sure that layout<T> doesn't fail at runtime.
281    const fn assert_layout() {
282        if BIN_OFFSET >= usize::BITS {
283            panic!("BIN_OFFSET is too large for the system's pointer width");
284        }
285
286        if BIN_OFFSET as usize + AMOUNT_OF_BINS >= usize::BITS as usize {
287            panic!("The combination of BIN_OFFSET and AMOUNT_OF_BINS exceeds usize capacity");
288        }
289
290        let max_elements = Self::bin_size(AMOUNT_OF_BINS - 1);
291
292        let size_of_t = core::mem::size_of::<T>();
293        if size_of_t > 0 && max_elements > (isize::MAX as usize / size_of_t) {
294            panic!("The largest bin exceeds isize::MAX bytes; Layout creation would fail");
295        }
296    }
297
298    /// Drop logic for this append only vec. We support skip_items here so this logic can be
299    /// reused for the IntoIterator logic where we some items have already been dropped if
300    /// ownership was taken.
301    fn drop_manual(&mut self, mut skip_items: usize) {
302        #[cfg(not(all(loom, test)))]
303        let mut remaining = *self.count.get_mut();
304
305        #[cfg(all(test, loom))]
306        let mut remaining = self.count.with_mut(|v| *v);
307
308        let is_zst = core::mem::size_of::<T>() == 0;
309
310        for (i, atomic_ptr) in self.data.iter_mut().enumerate() {
311            #[cfg(not(all(loom, test)))]
312            let bucket_ptr = *atomic_ptr.get_mut();
313
314            #[cfg(all(test, loom))]
315            let bucket_ptr = atomic_ptr.with_mut(|ptr| *ptr);
316
317            // Before `reserve()` was added we also stopped if remaining == 0`. However
318            // with reserve it's possible that we already created bins that have no items
319            // in them, so make sure to also clean those up.
320            if bucket_ptr.is_null() {
321                break;
322            }
323
324            let bin_cap = Self::bin_size(i);
325            let to_drop = core::cmp::min(remaining, bin_cap);
326
327            // Drop individual elements in the bucket
328
329            for offset in 0..to_drop {
330                if skip_items > 0 {
331                    skip_items -= 1;
332                } else {
333                    // Safety:
334                    // - self.count is used to calculate this pointers and guarantees we have allocated this ptr
335                    // - pointer is valid and alligned (we allocated a proper layout and use offset)
336                    // - we are the only ones de-allocating this memory
337                    unsafe {
338                        ptr::drop_in_place(bucket_ptr.add(offset));
339                    }
340                }
341            }
342
343            // Deallocate the bucket itself is not zst
344            if !is_zst {
345                #[allow(
346                    clippy::expect_used,
347                    reason = "constructor has checked this on creation"
348                )]
349                let layout = Layout::array::<T>(bin_cap).expect("Layout of array of T with cap");
350
351                // Safety:
352                // - We just allocated this ptr so it exists
353                // - Layout matches the exact layout of creation
354                unsafe { dealloc(bucket_ptr as *mut u8, layout) };
355            }
356
357            remaining -= to_drop;
358        }
359    }
360}
361
362fn spin_wait(failures: &mut usize) {
363    #[cfg(not(all(test, loom)))]
364    {
365        *failures += 1;
366        if *failures <= 10 {
367            core::hint::spin_loop();
368        } else {
369            #[cfg(feature = "std")]
370            std::thread::yield_now();
371
372            #[cfg(not(feature = "std"))]
373            core::hint::spin_loop();
374        }
375    }
376
377    #[cfg(all(test, loom))]
378    {
379        _ = failures;
380        loom::thread::yield_now();
381    }
382}
383
384// Safety:
385// - This vec is Send if and only if all items send
386unsafe impl<T: Send, const AMOUNT_OF_BINS: usize, const BIN_OFFSET: u32> Send
387    for AppendOnlyVec<T, AMOUNT_OF_BINS, BIN_OFFSET>
388{
389}
390
391// Safety:
392// - This vec is Sync if and only if all items Sync
393// - But it also needs Send for the entire collection to be sync
394unsafe impl<T: Send + Sync, const AMOUNT_OF_BINS: usize, const BIN_OFFSET: u32> Sync
395    for AppendOnlyVec<T, AMOUNT_OF_BINS, BIN_OFFSET>
396{
397}
398
399impl<T, const AMOUNT_OF_BINS: usize, const BIN_OFFSET: u32> Drop
400    for AppendOnlyVec<T, AMOUNT_OF_BINS, BIN_OFFSET>
401{
402    fn drop(&mut self) {
403        self.drop_manual(0);
404    }
405}
406
407impl<T, const AMOUNT_OF_BINS: usize, const BIN_OFFSET: u32> Default
408    for AppendOnlyVec<T, AMOUNT_OF_BINS, BIN_OFFSET>
409{
410    fn default() -> Self {
411        Self::new()
412    }
413}
414
415use core::ops::Index;
416
417impl<T, const AMOUNT_OF_BINS: usize, const BIN_OFFSET: u32> Index<usize>
418    for AppendOnlyVec<T, AMOUNT_OF_BINS, BIN_OFFSET>
419{
420    type Output = T;
421
422    fn index(&self, idx: usize) -> &Self::Output {
423        // Bounds check + Acquire ordering to ensure data visibility
424        assert!(idx < self.len(), "Index out of bounds");
425
426        // Safety: we just check if idx is within bounds
427        unsafe { self.get_unchecked(idx) }
428    }
429}
430
431/// A double-ended iterator for [`AppendOnlyVec`]
432pub struct Iter<'a, T, const AMOUNT_OF_BINS: usize, const BIN_OFFSET: u32> {
433    vec: &'a AppendOnlyVec<T, AMOUNT_OF_BINS, BIN_OFFSET>,
434    start: usize,
435    end: usize,
436}
437
438impl<'a, T, const AMOUNT_OF_BINS: usize, const BIN_OFFSET: u32> Iterator
439    for Iter<'a, T, AMOUNT_OF_BINS, BIN_OFFSET>
440{
441    type Item = &'a T;
442
443    fn next(&mut self) -> Option<Self::Item> {
444        if self.start < self.end {
445            let pos = self.start;
446            self.start += 1;
447            // Safety: We are within the snapshot bounds captured at creation
448            Some(unsafe { self.vec.get_unchecked(pos) })
449        } else {
450            None
451        }
452    }
453
454    fn size_hint(&self) -> (usize, Option<usize>) {
455        let len = self.end - self.start;
456        (len, Some(len))
457    }
458}
459
460impl<'a, T, const AMOUNT_OF_BINS: usize, const BIN_OFFSET: u32> DoubleEndedIterator
461    for Iter<'a, T, AMOUNT_OF_BINS, BIN_OFFSET>
462{
463    fn next_back(&mut self) -> Option<Self::Item> {
464        if self.start < self.end {
465            self.end -= 1;
466            let pos = self.end;
467            // Safety: We are within the snapshot bounds captured at creation
468            Some(unsafe { self.vec.get_unchecked(pos) })
469        } else {
470            None
471        }
472    }
473}
474
475impl<'a, T, const AMOUNT_OF_BINS: usize, const BIN_OFFSET: u32> ExactSizeIterator
476    for Iter<'a, T, AMOUNT_OF_BINS, BIN_OFFSET>
477{
478}
479
480impl<T, const AMOUNT_OF_BINS: usize, const BIN_OFFSET: u32> FromIterator<T>
481    for AppendOnlyVec<T, AMOUNT_OF_BINS, BIN_OFFSET>
482{
483    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
484        let this = Self::new();
485        for item in iter {
486            this.push(item);
487        }
488        this
489    }
490}
491
492impl<'a, T, const BINS: usize, const OFFSET: u32> IntoIterator
493    for &'a AppendOnlyVec<T, BINS, OFFSET>
494{
495    type Item = &'a T;
496    type IntoIter = Iter<'a, T, BINS, OFFSET>;
497
498    fn into_iter(self) -> Self::IntoIter {
499        self.iter()
500    }
501}
502
503pub struct IntoIterOwned<T, const BINS: usize, const OFFSET: u32> {
504    // We need to manually handle dropping of items that we didn't iter over
505    vec: ManuallyDrop<AppendOnlyVec<T, BINS, OFFSET>>,
506    consumed: usize,
507}
508
509impl<T, const BINS: usize, const OFFSET: u32> IntoIterator for AppendOnlyVec<T, BINS, OFFSET> {
510    type Item = T;
511    type IntoIter = IntoIterOwned<T, BINS, OFFSET>;
512
513    fn into_iter(self) -> Self::IntoIter {
514        IntoIterOwned {
515            vec: ManuallyDrop::new(self),
516            consumed: 0,
517        }
518    }
519}
520
521impl<T, const BINS: usize, const OFFSET: u32> Iterator for IntoIterOwned<T, BINS, OFFSET> {
522    type Item = T;
523
524    fn next(&mut self) -> Option<Self::Item> {
525        if self.consumed < self.vec.len() {
526            let idx = self.consumed;
527            self.consumed += 1;
528
529            let (bin_idx, offset) = AppendOnlyVec::<T, BINS, OFFSET>::indices(idx);
530            let bucket = self.vec.data[bin_idx].load(Ordering::Acquire);
531
532            // Safety: This is safe because consume < total, and since we own this
533            // structure no one else can change this
534            unsafe { Some(core::ptr::read(bucket.add(offset))) }
535        } else {
536            None
537        }
538    }
539
540    fn size_hint(&self) -> (usize, Option<usize>) {
541        let remaining = self.vec.len() - self.consumed;
542        (remaining, Some(remaining))
543    }
544}
545
546impl<T, const BINS: usize, const OFFSET: u32> Drop for IntoIterOwned<T, BINS, OFFSET> {
547    fn drop(&mut self) {
548        self.vec.drop_manual(self.consumed);
549    }
550}
551
552impl<T, const BINS: usize, const OFFSET: u32> Extend<T> for AppendOnlyVec<T, BINS, OFFSET> {
553    fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
554        for item in iter {
555            self.push(item);
556        }
557    }
558}
559
560// Since we only need &self to push items we can also implement this for &AppendOnlyVec
561
562impl<T, const BINS: usize, const OFFSET: u32> Extend<T> for &AppendOnlyVec<T, BINS, OFFSET> {
563    fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
564        for item in iter {
565            self.push(item);
566        }
567    }
568}
569
570#[cfg(all(test, not(loom)))]
571mod tests {
572    use super::*;
573
574    #[test]
575    fn we_can_add_items_and_iter_them() {
576        let vec: AppendOnlyVec<usize> = AppendOnlyVec::new();
577        vec.push(1);
578        vec.push(3);
579
580        let mut iter = vec.iter();
581        assert_eq!(iter.size_hint().0, 2);
582        assert_eq!(*iter.next().unwrap(), 1);
583        assert_eq!(*iter.next().unwrap(), 3);
584    }
585
586    #[derive(Clone, Debug)]
587    struct NoSize;
588
589    #[test]
590    fn support_zero_sized_types() {
591        let vec: AppendOnlyVec<NoSize> = AppendOnlyVec::new();
592        vec.push(NoSize);
593        vec.push(NoSize);
594    }
595
596    #[test]
597    fn push_crosses_bin_boundaries_with_stable_order() {
598        let vec: AppendOnlyVec<usize, 4, 3> = AppendOnlyVec::new();
599
600        for i in 0..26 {
601            assert_eq!(vec.push(i), i);
602        }
603
604        assert_eq!(vec.len(), 26);
605        assert_eq!(vec[7], 7);
606        assert_eq!(vec[8], 8);
607        assert_eq!(vec[23], 23);
608        assert_eq!(vec[24], 24);
609
610        let items: Vec<usize> = vec.iter().copied().collect();
611        assert_eq!(items, (0..26).collect::<Vec<_>>());
612    }
613
614    #[test]
615    fn iter_is_a_snapshot_of_length_at_creation() {
616        let vec: AppendOnlyVec<usize, 3, 1> = AppendOnlyVec::new();
617        vec.push(1);
618
619        let iter = vec.iter();
620        vec.push(2);
621
622        let items: Vec<usize> = iter.copied().collect();
623        assert_eq!(items, vec![1]);
624        assert_eq!(vec.len(), 2);
625    }
626
627    #[test]
628    #[should_panic(expected = "append only vec has exceeded max capacity")]
629    fn push_panics_when_capacity_is_exceeded() {
630        let vec: AppendOnlyVec<u8, 1, 1> = AppendOnlyVec::new();
631        assert_eq!(AppendOnlyVec::<u8, 1, 1>::capacity(), 2);
632
633        vec.push(1);
634        vec.push(2);
635        vec.push(3);
636    }
637}
638
639#[cfg(all(test, loom))]
640mod loom_tests {
641    use std::sync::Arc;
642
643    use loom::thread;
644
645    use super::*;
646
647    fn create_builder() -> loom::model::Builder {
648        let mut builder = loom::model::Builder::new();
649        builder.max_branches = 100000;
650        builder
651    }
652
653    #[test]
654    fn basic() {
655        create_builder().check(|| {
656            let vec: Arc<AppendOnlyVec<usize>> = Arc::new(AppendOnlyVec::new());
657            vec.push(8);
658
659            let vec_cl = vec.clone();
660
661            let x = thread::spawn(move || {
662                vec_cl.push(16);
663            });
664
665            x.join().unwrap();
666            assert_eq!(vec.len(), 2);
667        });
668    }
669
670    #[test]
671    fn concurrent_push() {
672        create_builder().check(|| {
673            let vec = Arc::new(AppendOnlyVec::<usize, 2, 1>::new());
674
675            let vec_cl = vec.clone();
676            let t1 = loom::thread::spawn(move || vec_cl.push(1));
677            let vec_cl = vec.clone();
678            let t2 = loom::thread::spawn(move || vec_cl.clone().push(2));
679
680            t1.join().unwrap();
681            t2.join().unwrap();
682            assert_eq!(vec.len(), 2);
683
684            // Ensure both values are present (order might vary)
685            let sum: usize = vec.iter().sum();
686            assert_eq!(sum, 3);
687        });
688    }
689
690    #[test]
691    fn read_while_push() {
692        create_builder().check(|| {
693            let vec = Arc::new(AppendOnlyVec::<usize, 2, 1>::new());
694            let v1 = vec.clone();
695
696            let t1 = loom::thread::spawn(move || {
697                v1.push(42);
698            });
699
700            // If len = 1 we should be able to read it, meaning len should only be updated after
701            // the data is available
702            if vec.len() == 1 {
703                assert_eq!(*vec.get(0).unwrap(), 42);
704            }
705
706            // Make sure to wait for this thread to finish so loom can cleanup everything while this
707            // closure is still active, otherwise it will panick
708            t1.join().unwrap();
709        });
710    }
711
712    // reserve() was removed because it's actually quite tricky to implement, but in case we ever
713    // add it again, this test can be used for it
714
715    // #[test]
716    // fn reserve_and_push() {
717    //     create_builder().check(|| {
718    //         let vec = Arc::new(AppendOnlyVec::<usize, 5, 1>::new());
719    //         let v1 = vec.clone();
720    //         let v2 = vec.clone();
721
722    //         // Both of these will race to allocate, but it should handle that
723    //         let t1 = loom::thread::spawn(move || v1.reserve(10));
724    //         let t2 = loom::thread::spawn(move || v2.push(100));
725
726    //         t1.join().unwrap();
727    //         t2.join().unwrap();
728    //     });
729    // }
730
731    #[derive(Clone, Debug)]
732    struct NoSize;
733
734    #[test]
735    fn zero_sized_types() {
736        create_builder().check(|| {
737            let vec = AppendOnlyVec::<NoSize, 5, 1>::new();
738
739            // Zero sized types should not cause memory leaks, or alloc errors
740            vec.push(NoSize);
741            vec.push(NoSize);
742        });
743    }
744
745    #[test]
746    fn drop_of_partial_consumed_into_iter() {
747        create_builder().check(|| {
748            let vec = AppendOnlyVec::<String, 2, 1>::new();
749            vec.push("a".to_owned());
750            vec.push("b".to_owned());
751            vec.push("c".to_owned());
752            vec.push("d".to_owned());
753
754            let mut iter = vec.into_iter();
755
756            let item = iter.next();
757            assert_eq!(item.unwrap(), "a");
758
759            // This should de-allocate all remaining items and the buckets
760            drop(iter);
761        });
762    }
763}