1
use std::array;
2
use std::cell::UnsafeCell;
3
use std::marker::PhantomData;
4
use std::mem::MaybeUninit;
5
use std::mem::needs_drop;
6
use std::ptr::null_mut;
7
use std::sync::atomic::AtomicPtr;
8
use std::sync::atomic::AtomicUsize;
9
use std::sync::atomic::Ordering;
10

            
11
use crossbeam_utils::CachePadded;
12
use thread_local::ThreadLocal;
13

            
14
/// Fixed number of buckets. A `usize`-indexed pool never needs more, because the
15
/// buckets grow geometrically and the largest reachable block index lands well
16
/// inside this range (checked in [`bucket_of_block`]).
17
const NUM_BUCKETS: usize = usize::BITS as usize;
18

            
19
/// Number of blocks held by the first bucket, expressed as a shift. Later
20
/// buckets double in size, so this also fixes how quickly the geometric growth
21
/// kicks in.
22
const FIRST_BITS: u32 = 4;
23
const FIRST_BLOCKS: usize = 1 << FIRST_BITS;
24

            
25
/// An append-only, thread-safe vector whose [`push`](Self::push) returns the
26
/// index the element landed at, trading dense indices for low write contention.
27
///
28
/// # Details
29
///
30
/// Where `boxcar::Vec` tags every slot with its own atomic flag and
31
/// `append-only-vec` keeps indices dense with a per-push compare-exchange spin,
32
/// this structure hands each thread a whole *block* of `BLOCK` consecutive
33
/// indices with a single `fetch_add` and lets it fill them with plain writes.
34
/// The shared cursor is therefore touched only once per `BLOCK` pushes, and a
35
/// thread's writes never contend with another's.
36
///
37
/// The price is that indices are not dense: a thread that stops pushing leaves
38
/// the unused tail of its current block as a permanent gap. [`len`](Self::len)
39
/// counts only the elements actually written, and [`get`](Self::get) returns
40
/// `None` for a gap.
41
///
42
/// Each block is published with one cache-line-padded counter (one per `BLOCK`
43
/// slots, not one per slot), which both makes a written slot visible to readers
44
/// and records exactly which slots to drop. Storage lives in geometrically
45
/// growing buckets, so the per-element overhead is the padded counter amortized
46
/// over a block plus the usual geometric slack — far below `boxcar`'s per-slot
47
/// flag.
48
pub struct ConcurrentAppendVec<T, const BLOCK: usize = 256> {
49
    /// Block reservation cursor. `fetch_add(1)` hands out the next block of
50
    /// `BLOCK` indices; the base index of block `b` is `b * BLOCK`.
51
    reserved_blocks: CachePadded<AtomicUsize>,
52
    /// Per-thread reservation cursor and element count. Padded so two threads'
53
    /// hot push state never share a cache line.
54
    local: ThreadLocal<CachePadded<Local>>,
55
    /// Geometrically growing, lazily allocated storage buckets.
56
    buckets: [AtomicPtr<Bucket<T>>; NUM_BUCKETS],
57
    /// Marks `T` as owned so the auto `Send`/`Sync` derivation depends on it;
58
    /// the real bounds are supplied by the manual `unsafe impl`s below.
59
    _marker: PhantomData<*mut T>,
60
}
61

            
62
/// SAFETY: the vector owns its `T` values; moving it to another thread is sound
63
/// exactly when `T` may move between threads.
64
unsafe impl<T: Send, const BLOCK: usize> Send for ConcurrentAppendVec<T, BLOCK> {}
65

            
66
/// SAFETY: sharing `&self` lets threads push values (dropped or read by other
67
/// threads, needing `T: Send`) and obtain `&T` (needing `T: Sync`). Per-slot
68
/// writes are published through the per-block release/acquire counter, so no two
69
/// accesses to the same slot race.
70
unsafe impl<T: Send + Sync, const BLOCK: usize> Sync for ConcurrentAppendVec<T, BLOCK> {}
71

            
72
/// Per-thread state: the half-open range `[next, end)` of indices still free in
73
/// the thread's current block, plus the number of elements it has pushed.
74
///
75
/// `next` and `end` are touched only by the owning thread, but they are atomic
76
/// so that the whole struct is `Sync` and [`ConcurrentAppendVec::len`] can
77
/// iterate every thread's `count`.
78
struct Local {
79
    /// Next free index in the current block.
80
    next: AtomicUsize,
81
    /// One past the last index of the current block; `next == end` means the
82
    /// block is exhausted and a new one must be reserved.
83
    end: AtomicUsize,
84
    /// Count of elements this thread has pushed.
85
    count: AtomicUsize,
86
}
87

            
88
impl Local {
89
13919
    fn new() -> Local {
90
13919
        Local {
91
13919
            next: AtomicUsize::new(0),
92
13919
            end: AtomicUsize::new(0),
93
13919
            count: AtomicUsize::new(0),
94
13919
        }
95
13919
    }
96
}
97

            
98
/// One storage bucket: a run of slots plus a published length per block.
99
struct Bucket<T> {
100
    /// `block_count * BLOCK` slots, written through `&self` so guarded by
101
    /// `UnsafeCell`.
102
    slots: Box<[UnsafeCell<MaybeUninit<T>>]>,
103
    /// Number of initialized slots at the front of each block. Padded to a cache
104
    /// line so a thread publishing its block does not falsely share with the
105
    /// thread filling the neighbouring block.
106
    commits: Box<[CachePadded<AtomicUsize>]>,
107
}
108

            
109
impl<T> Bucket<T> {
110
715
    fn new(slot_count: usize, block_count: usize) -> Bucket<T> {
111
715
        let slots = (0..slot_count)
112
2302912
            .map(|_| UnsafeCell::new(MaybeUninit::uninit()))
113
715
            .collect::<Vec<_>>()
114
715
            .into_boxed_slice();
115
715
        let commits = (0..block_count)
116
12256
            .map(|_| CachePadded::new(AtomicUsize::new(0)))
117
715
            .collect::<Vec<_>>()
118
715
            .into_boxed_slice();
119
715
        Bucket { slots, commits }
120
715
    }
121
}
122

            
123
impl<T> Drop for Bucket<T> {
124
715
    fn drop(&mut self) {
125
715
        if needs_drop::<T>() {
126
3
            let block_size = self.slots.len() / self.commits.len();
127
48
            for (block, commit) in self.commits.iter().enumerate() {
128
48
                let committed = commit.load(Ordering::Relaxed);
129
48
                for offset in 0..committed {
130
18
                    let slot = block * block_size + offset;
131
                    // SAFETY: a committed slot was written and is uniquely owned
132
                    // here (`&mut self`), so dropping it once is sound.
133
18
                    unsafe {
134
18
                        (*self.slots[slot].get()).assume_init_drop();
135
18
                    }
136
                }
137
            }
138
712
        }
139
715
    }
140
}
141

            
142
impl<T, const BLOCK: usize> ConcurrentAppendVec<T, BLOCK> {
143
    /// Base-two logarithm of `BLOCK`, valid because `BLOCK` is a power of two.
144
    const BLOCK_BITS: u32 = BLOCK.trailing_zeros();
145

            
146
    /// Creates a new empty vector.
147
616
    pub fn new() -> ConcurrentAppendVec<T, BLOCK> {
148
        const {
149
            assert!(
150
                BLOCK.is_power_of_two() && BLOCK >= 2,
151
                "BLOCK must be a power of two >= 2"
152
            )
153
        };
154
        ConcurrentAppendVec {
155
616
            reserved_blocks: CachePadded::new(AtomicUsize::new(0)),
156
616
            local: ThreadLocal::new(),
157
39424
            buckets: array::from_fn(|_| AtomicPtr::new(null_mut())),
158
616
            _marker: PhantomData,
159
        }
160
616
    }
161

            
162
    /// Appends `value` and returns the index it was stored at. The returned
163
    /// indices are unique but not necessarily contiguous.
164
264257
    pub fn push(&self, value: T) -> usize {
165
264257
        let local = self.local.get_or(|| CachePadded::new(Local::new()));
166

            
167
        // Reserve a fresh block only once the current one is exhausted, so the
168
        // shared cursor is hit at most once per `BLOCK` pushes. Only this thread
169
        // touches `next`/`end`, so relaxed ordering suffices.
170
264257
        let mut next = local.next.load(Ordering::Relaxed);
171
264257
        if next == local.end.load(Ordering::Relaxed) {
172
3977
            let block = self.reserved_blocks.fetch_add(1, Ordering::Relaxed);
173
3977
            next = block << Self::BLOCK_BITS;
174
3977
            local.end.store(next + BLOCK, Ordering::Relaxed);
175
260280
        }
176
264257
        local.next.store(next + 1, Ordering::Relaxed);
177
264257
        local.count.fetch_add(1, Ordering::Relaxed);
178

            
179
264257
        let index = next;
180
264257
        let (bucket_index, block, offset) = Self::locate(index);
181
264257
        let bucket = self.bucket_or_alloc(bucket_index);
182
264257
        let slot = block * BLOCK + offset;
183

            
184
        // SAFETY: `index` belongs to a block this thread reserved exclusively, so
185
        // no other thread writes or reads this slot until the commit store below
186
        // publishes it; the write therefore does not race.
187
264257
        unsafe {
188
264257
            (*bucket.slots[slot].get()).write(value);
189
264257
        }
190

            
191
        // Single writer per block, so the published length only grows; the
192
        // release pairs with the acquire in `get`/`iter` to expose the write.
193
264257
        bucket.commits[block].store(offset + 1, Ordering::Release);
194
264257
        index
195
264257
    }
196

            
197
    /// Returns the element at `index`, or `None` if `index` was never written
198
    /// (out of range or a gap left by block reservation).
199
104065
    pub fn get(&self, index: usize) -> Option<&T> {
200
104065
        let (bucket_index, block, offset) = Self::locate(index);
201
        // SAFETY: a bucket pointer is null or a `Box` we own and keep alive until
202
        // `&mut self` is taken; the acquire pairs with the allocating release.
203
104065
        let bucket = unsafe { self.buckets[bucket_index].load(Ordering::Acquire).as_ref()? };
204
104063
        let committed = bucket.commits[block].load(Ordering::Acquire);
205
104063
        if offset < committed {
206
102731
            let slot = block * BLOCK + offset;
207
            // SAFETY: `offset < committed` means the slot was written before the
208
            // release we just synchronized with, so it is initialized and, being
209
            // append-only, never mutated again.
210
102731
            Some(unsafe { (*bucket.slots[slot].get()).assume_init_ref() })
211
        } else {
212
1332
            None
213
        }
214
104065
    }
215

            
216
    /// Returns the element at `index` without verifying that it was written.
217
    ///
218
    /// # Safety
219
    ///
220
    /// `index` must have been returned by a previous [`push`](Self::push) whose
221
    /// effect is visible to the caller through external synchronization (a lock,
222
    /// or joining the producing thread). Passing an out-of-range index or a
223
    /// reserved-but-unwritten gap is undefined behavior. Skipping the per-block
224
    /// commit load makes this cheaper than [`get`](Self::get) on hot read paths.
225
1234096
    pub unsafe fn get_unchecked(&self, index: usize) -> &T {
226
1234096
        let (bucket_index, block, offset) = Self::locate(index);
227
1234096
        let pointer = self.buckets[bucket_index].load(Ordering::Acquire);
228
1234096
        debug_assert!(!pointer.is_null(), "get_unchecked on an unallocated bucket");
229
1234096
        let slot = block * BLOCK + offset;
230
        // SAFETY: by the contract `index` came from a published `push`, so its
231
        // bucket is allocated and the slot initialized, and that push
232
        // happens-before this read; reading the slot is therefore sound.
233
        unsafe {
234
1234096
            let bucket = &*pointer;
235
1234096
            (*bucket.slots[slot].get()).assume_init_ref()
236
        }
237
1234096
    }
238

            
239
    /// Returns the number of elements written, summed across threads.
240
    ///
241
    /// This walks every thread that has pushed, so it is `O(threads)`; it is a
242
    /// snapshot only when no push runs concurrently.
243
22731
    pub fn len(&self) -> usize {
244
22746
        self.local.iter().map(|local| local.count.load(Ordering::Relaxed)).sum()
245
22731
    }
246

            
247
    /// Returns true if no element has been written.
248
    pub fn is_empty(&self) -> bool {
249
        self.len() == 0
250
    }
251

            
252
    /// Iterates the written elements in index order, skipping gaps.
253
51
    pub fn iter(&self) -> impl Iterator<Item = &T> {
254
51
        let high_water = self.reserved_blocks.load(Ordering::Acquire) << Self::BLOCK_BITS;
255
11536
        (0..high_water).filter_map(move |index| self.get(index))
256
51
    }
257

            
258
    /// Removes every element, invalidating all previously returned indices.
259
1
    pub fn clear(&mut self) {
260
64
        for bucket in &mut self.buckets {
261
64
            let pointer = *bucket.get_mut();
262
64
            if !pointer.is_null() {
263
1
                // SAFETY: `&mut self` is exclusive, so reclaiming the owned box
264
1
                // (which drops its initialized slots) cannot race.
265
1
                unsafe {
266
1
                    drop(Box::from_raw(pointer));
267
1
                }
268
1
                *bucket.get_mut() = null_mut();
269
63
            }
270
        }
271
1
        self.reserved_blocks.store(0, Ordering::Relaxed);
272
1
        self.local = ThreadLocal::new();
273
1
    }
274

            
275
    /// Returns the bucket for `bucket_index`, allocating it on first use.
276
264257
    fn bucket_or_alloc(&self, bucket_index: usize) -> &Bucket<T> {
277
264257
        let slot = &self.buckets[bucket_index];
278
264257
        let pointer = slot.load(Ordering::Acquire);
279
264257
        if !pointer.is_null() {
280
            // SAFETY: non-null means a `Box` we allocated and still own.
281
263542
            return unsafe { &*pointer };
282
715
        }
283

            
284
715
        let block_count = bucket_blocks(bucket_index);
285
715
        let allocated = Box::into_raw(Box::new(Bucket::<T>::new(block_count * BLOCK, block_count)));
286
715
        match slot.compare_exchange(null_mut(), allocated, Ordering::Release, Ordering::Acquire) {
287
            // SAFETY: we published `allocated`; it stays valid until `&mut self`.
288
713
            Ok(_) => unsafe { &*allocated },
289
2
            Err(winner) => {
290
                // SAFETY: another thread won the race; reclaim our unused
291
                // allocation and use the published one instead.
292
                unsafe {
293
2
                    drop(Box::from_raw(allocated));
294
2
                    &*winner
295
                }
296
            }
297
        }
298
264257
    }
299

            
300
    /// Splits `index` into its bucket, block offset within the bucket, and offset
301
    /// within the block.
302
1602418
    fn locate(index: usize) -> (usize, usize, usize) {
303
1602418
        let block = index >> Self::BLOCK_BITS;
304
1602418
        let bucket_index = bucket_of_block(block);
305
1602418
        let block_in_bucket = block - bucket_start_blocks(bucket_index);
306
1602418
        let offset = index & (BLOCK - 1);
307
1602418
        (bucket_index, block_in_bucket, offset)
308
1602418
    }
309
}
310

            
311
impl<T, const BLOCK: usize> Default for ConcurrentAppendVec<T, BLOCK> {
312
    fn default() -> ConcurrentAppendVec<T, BLOCK> {
313
        ConcurrentAppendVec::new()
314
    }
315
}
316

            
317
impl<T, const BLOCK: usize> Drop for ConcurrentAppendVec<T, BLOCK> {
318
616
    fn drop(&mut self) {
319
39424
        for bucket in &mut self.buckets {
320
39424
            let pointer = *bucket.get_mut();
321
39424
            if !pointer.is_null() {
322
                // SAFETY: at drop we hold exclusive access; the box drops its
323
                // initialized slots and frees its storage exactly once.
324
712
                unsafe {
325
712
                    drop(Box::from_raw(pointer));
326
712
                }
327
38712
            }
328
        }
329
616
    }
330
}
331

            
332
/// Returns the bucket holding block `block`.
333
34050910
fn bucket_of_block(block: usize) -> usize {
334
34050910
    if block < FIRST_BLOCKS {
335
33618789
        0
336
    } else {
337
432121
        (usize::BITS - (block >> FIRST_BITS).leading_zeros()) as usize
338
    }
339
34050910
}
340

            
341
/// Returns the first block index stored in bucket `bucket`.
342
34050910
fn bucket_start_blocks(bucket: usize) -> usize {
343
34050910
    if bucket == 0 { 0 } else { FIRST_BLOCKS << (bucket - 1) }
344
34050910
}
345

            
346
/// Returns the number of blocks stored in bucket `bucket`.
347
12307
fn bucket_blocks(bucket: usize) -> usize {
348
12307
    if bucket == 0 {
349
12132
        FIRST_BLOCKS
350
    } else {
351
175
        FIRST_BLOCKS << (bucket - 1)
352
    }
353
12307
}
354

            
355
#[cfg(test)]
356
mod tests {
357
    use std::collections::HashMap;
358
    use std::collections::HashSet;
359
    use std::sync::Arc;
360
    use std::sync::Mutex;
361
    use std::sync::atomic::AtomicUsize;
362
    use std::sync::atomic::Ordering;
363

            
364
    use rand::RngExt;
365

            
366
    use merc_utilities::random_test;
367
    use merc_utilities::random_test_threads;
368

            
369
    use super::ConcurrentAppendVec;
370

            
371
    /// Pushes random values, checking that every returned index resolves to the
372
    /// value pushed, that gaps and out-of-range indices read back as `None`, and
373
    /// that the length matches the number of pushes.
374
    #[test]
375
    #[cfg_attr(miri, ignore)]
376
1
    fn random_push_get_len() {
377
50
        random_test(50, |rng| {
378
            // A small block size makes gaps frequent so they are exercised.
379
50
            let vec: ConcurrentAppendVec<u64, 4> = ConcurrentAppendVec::new();
380
50
            let mut model: HashMap<usize, u64> = HashMap::new();
381

            
382
50
            for _ in 0..200 {
383
10000
                let value = rng.random_range(0..1000u64);
384
10000
                let index = vec.push(value);
385
10000
                assert!(model.insert(index, value).is_none(), "indices are unique");
386
10000
                assert_eq!(vec.get(index), Some(&value), "index resolves to its value");
387
            }
388

            
389
50
            assert_eq!(vec.len(), model.len(), "length counts written elements");
390
10000
            for (&index, &value) in &model {
391
10000
                assert_eq!(vec.get(index), Some(&value));
392
            }
393

            
394
50
            let collected: Vec<u64> = vec.iter().copied().collect();
395
50
            assert_eq!(collected.len(), model.len(), "iter visits every written element once");
396
50
        });
397
1
    }
398

            
399
    /// A single short block leaves the rest of the reserved block as gaps.
400
    #[test]
401
1
    fn gaps_read_back_as_none() {
402
1
        let vec: ConcurrentAppendVec<u64, 4> = ConcurrentAppendVec::new();
403
1
        let i0 = vec.push(10);
404
1
        let i1 = vec.push(20);
405
1
        assert_eq!((i0, i1), (0, 1));
406
1
        assert_eq!(vec.get(0), Some(&10));
407
1
        assert_eq!(vec.get(1), Some(&20));
408
1
        assert_eq!(vec.get(2), None, "reserved but unwritten slot is a gap");
409
1
        assert_eq!(vec.get(3), None);
410
1
        assert_eq!(vec.get(1000), None, "out of range");
411
1
        assert_eq!(vec.len(), 2);
412
1
    }
413

            
414
    /// `get_unchecked` returns the same reference as `get` for written indices,
415
    /// across the block and bucket boundaries that a small `BLOCK` produces.
416
    #[test]
417
1
    fn get_unchecked_matches_get() {
418
1
        let vec: ConcurrentAppendVec<u64, 4> = ConcurrentAppendVec::new();
419
40
        let entries: Vec<(usize, u64)> = (0..40u64).map(|value| (vec.push(value * 7), value * 7)).collect();
420
40
        for (index, value) in entries {
421
            // SAFETY: `index` was just returned by `push` on this thread.
422
40
            assert_eq!(unsafe { vec.get_unchecked(index) }, &value);
423
40
            assert_eq!(vec.get(index), Some(&value));
424
        }
425
1
    }
426

            
427
    /// Pushes across many threads, then verifies after the join that every
428
    /// returned index still resolves and that indices are globally unique.
429
    #[test]
430
    #[cfg_attr(miri, ignore)]
431
1
    fn random_concurrent_push() {
432
1
        let vec: Arc<ConcurrentAppendVec<u64>> = Arc::new(ConcurrentAppendVec::new());
433
1
        let results: Arc<Mutex<Vec<(usize, u64)>>> = Arc::new(Mutex::new(Vec::new()));
434

            
435
1
        random_test_threads(
436
            1000,
437
            8,
438
8
            || (Arc::clone(&vec), Arc::clone(&results)),
439
8000
            move |rng, (vec, results)| {
440
8000
                let value = rng.random_range(0..1_000_000u64);
441
8000
                let index = vec.push(value);
442
8000
                results.lock().unwrap().push((index, value));
443
8000
            },
444
        );
445

            
446
1
        let results = results.lock().unwrap();
447
1
        let mut seen = HashSet::new();
448
8000
        for &(index, value) in results.iter() {
449
8000
            assert!(seen.insert(index), "indices are globally unique");
450
8000
            assert_eq!(vec.get(index), Some(&value), "index resolves after the join");
451
        }
452
1
        assert_eq!(vec.len(), results.len(), "length counts every push");
453
1
    }
454

            
455
    /// A small concurrent smoke test kept light enough for Miri's data-race
456
    /// detector: threads push and concurrently read back recorded indices,
457
    /// exercising the per-block release/acquire publication and the bucket
458
    /// allocation race.
459
    #[test]
460
1
    fn concurrent_push_and_get() {
461
1
        let vec: Arc<ConcurrentAppendVec<u64, 4>> = Arc::new(ConcurrentAppendVec::new());
462
1
        let recorded: Arc<Mutex<Vec<(usize, u64)>>> = Arc::new(Mutex::new(Vec::new()));
463

            
464
1
        let threads: Vec<_> = (0..4u64)
465
4
            .map(|thread| {
466
4
                let vec = Arc::clone(&vec);
467
4
                let recorded = Arc::clone(&recorded);
468
4
                std::thread::spawn(move || {
469
100
                    for step in 0..25u64 {
470
100
                        let value = thread * 1000 + step;
471
100
                        let index = vec.push(value);
472
100
                        recorded.lock().unwrap().push((index, value));
473

            
474
                        // Read back an arbitrary already-recorded entry, which
475
                        // may belong to another thread.
476
100
                        let entry = {
477
100
                            let guard = recorded.lock().unwrap();
478
100
                            guard[(value as usize) % guard.len()]
479
                        };
480
100
                        assert_eq!(vec.get(entry.0), Some(&entry.1));
481
                    }
482
4
                })
483
4
            })
484
1
            .collect();
485
4
        for thread in threads {
486
4
            thread.join().unwrap();
487
4
        }
488

            
489
1
        let recorded = recorded.lock().unwrap();
490
1
        assert_eq!(vec.len(), recorded.len());
491
100
        for &(index, value) in recorded.iter() {
492
100
            assert_eq!(vec.get(index), Some(&value));
493
        }
494
1
    }
495

            
496
    /// Verifies that dropping (and clearing) the vector drops each written value
497
    /// exactly once, even with the gaps that block reservation leaves behind.
498
    #[test]
499
1
    fn drops_each_written_value_once() {
500
        struct DropCounter(Arc<AtomicUsize>);
501
        impl Drop for DropCounter {
502
18
            fn drop(&mut self) {
503
18
                self.0.fetch_add(1, Ordering::Relaxed);
504
18
            }
505
        }
506

            
507
1
        let drops = Arc::new(AtomicUsize::new(0));
508

            
509
        {
510
1
            let vec: ConcurrentAppendVec<DropCounter, 4> = ConcurrentAppendVec::new();
511
10
            for _ in 0..10 {
512
10
                vec.push(DropCounter(Arc::clone(&drops)));
513
10
            }
514
            // Ten pushes over blocks of four leave two trailing gaps.
515
1
            assert_eq!(vec.len(), 10);
516
        }
517
1
        assert_eq!(
518
1
            drops.load(Ordering::Relaxed),
519
            10,
520
            "dropping frees every written value once"
521
        );
522

            
523
1
        drops.store(0, Ordering::Relaxed);
524
1
        let mut vec: ConcurrentAppendVec<DropCounter, 4> = ConcurrentAppendVec::new();
525
7
        for _ in 0..7 {
526
7
            vec.push(DropCounter(Arc::clone(&drops)));
527
7
        }
528
1
        vec.clear();
529
1
        assert_eq!(drops.load(Ordering::Relaxed), 7, "clear frees every written value once");
530
1
        assert_eq!(vec.len(), 0, "clear empties the vector");
531
1
        assert!(vec.get(0).is_none());
532

            
533
        // The cleared vector is reusable.
534
1
        assert_eq!(vec.push(DropCounter(Arc::clone(&drops))), 0);
535
1
    }
536
}