1
use std::alloc::Layout;
2
use std::array;
3
use std::cell::Cell;
4
use std::cell::UnsafeCell;
5
use std::marker::PhantomData;
6
use std::mem::ManuallyDrop;
7
use std::ptr::NonNull;
8
use std::sync::Mutex;
9
use std::sync::MutexGuard;
10

            
11
use allocator_api2::alloc::AllocError;
12
use allocator_api2::alloc::Allocator;
13
use thread_local::ThreadLocal;
14

            
15
use crate::FreeList;
16
use crate::FreeListEntry;
17

            
18
/// The number of entries in every free list chunk.
19
const FREE_LIST_CHUNK_SIZE: usize = 1000;
20

            
21
/// This is a memory pool or also called fixed-size block allocator for a
22
/// concrete type `T`. It stores blocks of `N` to minimize the overhead of
23
/// individual memory allocations, which are typically in the range of one or
24
/// two words.
25
///
26
/// Behaves like `Allocator`, except that it only allocates for layouts of `T`.
27
/// Requires periodic calls to `remove_free_blocks` to prevent memory usage from
28
/// growing indefinitely.
29
///
30
/// This allocator minimizes contention by maintaining per-thread state for
31
/// the common allocation/deallocation paths and only takes a lock when a new
32
/// block needs to be allocated. This does mean that external synchronisation
33
/// is required to prevent concurrent allocations overlapping with `remove_free_blocks`.
34
///
35
/// A single thread-local freelist is maintained per thread:
36
/// - `free`: popped from during allocation and pushed to during deallocation.
37
pub struct BlockAllocator<T: Send, const N: usize> {
38
    /// Owns the block list; only locked when a new block must be allocated.
39
    /// This is the only shared state accessed during allocation.
40
    blocks: Mutex<BlockList<T, N>>,
41

            
42
    /// Per-thread state for allocation: current block and bump offset. This eliminates
43
    /// contention in the common path.
44
    alloc_state: ThreadLocal<ThreadLocalAllocState<T, N>>,
45
}
46

            
47
/// The block list and bump pointer, protected by the blocks mutex.
48
struct BlockList<T, const N: usize> {
49
    /// The block that is currently being bump-allocated from. We avoid Box here
50
    /// to allow multiple blocks to point to the same next block without
51
    /// violating Box's noalias.
52
    head_block: Option<NonNull<Block<T, N>>>,
53

            
54
    /// Shared chunks of free entries represented by list heads. Each head
55
    /// points to a null-terminated list of length `FREE_LIST_CHUNK_SIZE`
56
    /// (except possibly the final chunk).
57
    free_chunks: Vec<NonNull<Entry<T>>>,
58
}
59

            
60
impl<T, const N: usize> Drop for BlockList<T, N> {
61
101
    fn drop(&mut self) {
62
        // Drop the head block; Block's Drop impl recursively drops the list.
63
101
        if let Some(block_ptr) = self.head_block.take() {
64
101
            // Safety: we own all blocks in the list.
65
101
            unsafe { drop(Box::from_raw(block_ptr.as_ptr())) };
66
101
        }
67
101
    }
68
}
69

            
70
impl<T: Send, const N: usize> Default for BlockAllocator<T, N> {
71
    fn default() -> Self {
72
        Self::new()
73
    }
74
}
75

            
76
impl<T: Send, const N: usize> BlockAllocator<T, N> {
77
9691
    pub fn new() -> Self {
78
9691
        Self {
79
9691
            blocks: Mutex::new(BlockList {
80
9691
                head_block: None,
81
9691
                free_chunks: Vec::new(),
82
9691
            }),
83
9691
            alloc_state: ThreadLocal::new(),
84
9691
        }
85
9691
    }
86

            
87
    /// Allocates a slot for one object of type `T`.
88
14172352
    pub fn allocate_object(&self) -> Result<NonNull<T>, AllocError> {
89
14172352
        let state = self.alloc_state.get_or(ThreadLocalAllocState::new);
90

            
91
        // Fast path 1: try thread-local free list.
92
14172352
        if let Some(entry) = state.free.try_pop() {
93
13014259
            return Ok(entry.cast());
94
1158093
        }
95

            
96
1158093
        if self.refill_local_free_from_chunks(state)
97
680812
            && let Some(entry) = state.free.try_pop()
98
        {
99
680812
            return Ok(entry.cast());
100
477281
        }
101

            
102
        // Fast path 2: lock-free bump allocation from current thread's block.
103
477281
        let block_ptr = state.current_block.get();
104
477281
        let offset = state.bump_offset.get();
105
477281
        if !block_ptr.is_null() && offset < N {
106
468327
            state.bump_offset.set(offset + 1);
107
            return unsafe {
108
468327
                let data_ptr = (*block_ptr).data.get() as *mut Entry<T>;
109
468327
                let entry_ptr = data_ptr.add(offset);
110
468327
                Ok(NonNull::new_unchecked(
111
468327
                    std::ptr::addr_of_mut!((*entry_ptr).data) as *mut T
112
468327
                ))
113
            };
114
8954
        }
115

            
116
        // Slow path: allocate a new block under the mutex.
117
8954
        self.allocate_new_block(state)
118
14172352
    }
119

            
120
    /// Refills the calling thread's local freelist from one shared chunk.
121
1158093
    fn refill_local_free_from_chunks(&self, state: &ThreadLocalAllocState<T, N>) -> bool {
122
1158093
        let mut guard = self.blocks.lock().expect("Lock poisoned");
123
1158093
        let Some(current) = guard.free_chunks.pop() else {
124
477281
            return false;
125
        };
126
680812
        drop(guard);
127

            
128
680812
        debug_assert!(
129
680812
            state.free.is_empty(),
130
            "local freelist must be empty before chunk refill"
131
        );
132
680812
        unsafe {
133
680812
            state.free.set_head(current.as_ptr());
134
680812
        }
135
680812
        true
136
1158093
    }
137

            
138
    /// Slow path: allocate a new block and update thread-local state.
139
    #[cold]
140
8954
    fn allocate_new_block(&self, state: &ThreadLocalAllocState<T, N>) -> Result<NonNull<T>, AllocError> {
141
8954
        let mut guard = self.blocks.lock().expect("Lock poisoned");
142

            
143
        // Allocate a new block and link it to the existing list.
144
8954
        let mut new_block = Block::new();
145
8954
        new_block.next = guard.head_block;
146
8954
        let new_block_ptr = unsafe { NonNull::new_unchecked(Box::into_raw(Box::new(new_block))) };
147
8954
        guard.head_block = Some(new_block_ptr);
148

            
149
8954
        drop(guard);
150

            
151
        // Update thread-local state with new block.
152
8954
        state.current_block.set(new_block_ptr.as_ptr());
153
8954
        state.bump_offset.set(1);
154

            
155
        // Return slot 0 of the new block.
156
        unsafe {
157
8954
            let data_ptr = (*new_block_ptr.as_ptr()).data.get() as *mut Entry<T>;
158
8954
            Ok(NonNull::new_unchecked(
159
8954
                std::ptr::addr_of_mut!((*data_ptr).data) as *mut T
160
8954
            ))
161
        }
162
8954
    }
163

            
164
    /// Deallocates a previously-allocated pointer.
165
13830029
    pub fn deallocate_object(&self, ptr: NonNull<T>) {
166
13830029
        let state = self.alloc_state.get_or(ThreadLocalAllocState::new);
167
13830029
        state.free.push(ptr.cast());
168
13830029
    }
169

            
170
    /// Removes empty blocks from the block list.
171
    ///
172
    /// Should be called periodically to prevent memory usage from growing
173
    /// indefinitely.
174
    ///
175
    /// **Important**: This method must not be called concurrently with any
176
    /// allocations or deallocations. It should be called at a synchronization
177
    /// point where no other threads are active on this allocator.
178
    ///
179
    /// This method scans every thread-local `free` list, marks those entries
180
    /// with a sentinel value, then removes blocks where all entries are marked
181
    /// as free.
182
    ///
183
    /// Returns `(removed_blocks, free_size)`: the number of blocks removed, and
184
    /// the size of the merged `free` list before cleanup.
185
3884170
    pub fn remove_free_blocks(&mut self) -> (usize, usize)
186
3884170
    where
187
3884170
        T: BlockAllocatorSafe,
188
    {
189
        // Mark all elements in all thread-local freelists with a special
190
        // value that none of the live entries can have.
191
3884170
        let nonexisting_value = NONEXISTING_VALUE as *mut Entry<T>;
192

            
193
        // In debug mode, collect all freelist entry pointers.
194
        #[cfg(debug_assertions)]
195
3884170
        let mut freelist_ptrs: std::collections::HashSet<*mut Entry<T>> = std::collections::HashSet::new();
196

            
197
        // Helper: walk a freelist and mark every entry with the sentinel.
198
        // We only update previous entries to ensure that iter keeps working.
199
        // Returns the number of entries in the freelist.
200
3884170
        let mark_freelist =
201
            |list: &FreeList<Entry<T>>,
202
             #[cfg(debug_assertions)] ptrs: &mut std::collections::HashSet<*mut Entry<T>>| unsafe {
203
1991656
                let mut previous: Option<NonNull<Entry<T>>> = None;
204
1991656
                let mut count: usize = 0;
205
318401724
                for current in list.iter() {
206
                    #[cfg(debug_assertions)]
207
318401724
                    ptrs.insert(current.as_ptr());
208

            
209
318401724
                    if let Some(previous) = previous {
210
317708159
                        *(*previous.as_ptr()).next = nonexisting_value;
211
317708159
                    }
212
318401724
                    previous = Some(current);
213
318401724
                    count += 1;
214
                }
215

            
216
1991656
                if let Some(previous) = previous {
217
693565
                    *(*previous.as_ptr()).next = nonexisting_value;
218
1298191
                }
219
1991656
                count
220
1991656
            };
221

            
222
3884170
        let mut free_size = 0;
223
3884170
        for state in self.alloc_state.iter_mut() {
224
1991656
            free_size += mark_freelist(
225
1991656
                &state.free,
226
1991656
                #[cfg(debug_assertions)]
227
1991656
                &mut freelist_ptrs,
228
1991656
            );
229
1991656
            state.free.clear();
230
1991656
            state.current_block.set(std::ptr::null_mut());
231
1991656
            state.bump_offset.set(N);
232
1991656
        }
233

            
234
3884170
        let mut guard = self.blocks.lock().expect("Lock poisoned");
235

            
236
        // Mark entries currently staged in shared free chunks.
237
3884170
        for &chunk_head in &guard.free_chunks {
238
330511
            let mut current = Some(chunk_head);
239
24898131
            while let Some(entry) = current {
240
24567620
                #[cfg(debug_assertions)]
241
24567620
                freelist_ptrs.insert(entry.as_ptr());
242
24567620

            
243
24567620
                let next = unsafe { Entry::get_next(entry.as_ptr()) };
244
24567620
                unsafe {
245
24567620
                    *(*entry.as_ptr()).next = nonexisting_value;
246
24567620
                }
247
24567620
                free_size += 1;
248
24567620
                current = NonNull::new(next);
249
24567620
            }
250
        }
251
3884170
        guard.free_chunks.clear();
252

            
253
        // Debug check: verify that no live entry has the sentinel value.
254
        #[cfg(debug_assertions)]
255
        {
256
3887270
            for block_ptr in Self::iter_blocks(&guard) {
257
2020338
                let data = unsafe { &*(*block_ptr.as_ptr()).data.get() };
258
2065651712
                for entry in data {
259
2065651712
                    let entry_ptr = entry as *const Entry<T> as *mut Entry<T>;
260
2065651712
                    if !freelist_ptrs.contains(&entry_ptr) {
261
                        // This entry is live — it must not look like the sentinel.
262
                        unsafe {
263
1722682368
                            debug_assert!(
264
1722682368
                                !std::ptr::eq(*entry.next, nonexisting_value),
265
                                "Live entry at {entry_ptr:?} has the sentinel value (null in first word). \
266
                                 This violates the BlockAllocatorSafe contract."
267
                            );
268
                        }
269
342969344
                    }
270
                }
271
            }
272
        }
273

            
274
3884170
        let removed = if guard.head_block.is_some() {
275
            // Remove blocks that are now empty, i.e., all their entries have nonexisting_value.
276
1991656
            let mut prev_next_field: *mut Option<NonNull<Block<T, N>>> = &mut guard.head_block;
277
1991656
            let mut removed_blocks = 0;
278

            
279
4011994
            while let Some(current_ptr) = unsafe { *prev_next_field } {
280
2020338
                let all_free = unsafe {
281
2020338
                    let data = &*(*current_ptr.as_ptr()).data.get();
282
34330123
                    data.iter().all(|entry| std::ptr::eq(*entry.next, nonexisting_value))
283
                };
284

            
285
2020338
                if all_free {
286
                    // Unlink and drop the current block.
287
                    let next = unsafe { (*current_ptr.as_ptr()).next.take() };
288
                    unsafe { *prev_next_field = next };
289
                    unsafe { drop(Box::from_raw(current_ptr.as_ptr())) };
290
                    removed_blocks += 1;
291
                    // prev_next_field stays the same — it now points to the next block.
292
2020338
                } else {
293
2020338
                    // Keep this block; advance to next.
294
2020338
                    prev_next_field = unsafe { &mut (*current_ptr.as_ptr()).next };
295
2020338
                }
296
            }
297

            
298
1991656
            removed_blocks
299
        } else {
300
            // No blocks, nothing to remove.
301
1892514
            0
302
        };
303

            
304
        // Recreate shared free chunks from remaining blocks.
305
3884170
        let mut rebuilt_chunk_heads: Vec<NonNull<Entry<T>>> = Vec::new();
306
3884170
        let mut chunk_head: Option<NonNull<Entry<T>>> = None;
307
3884170
        let mut chunk_tail: Option<NonNull<Entry<T>>> = None;
308
3884170
        let mut chunk_len = 0usize;
309

            
310
3887270
        for block_ptr in Self::iter_blocks(&guard) {
311
            // Walk entries via raw pointers; deriving `*mut` from `&Entry<T>`
312
            // would violate Stacked Borrows when we write through that pointer.
313
2020338
            let data_ptr = unsafe { (*block_ptr.as_ptr()).data.get() as *mut Entry<T> };
314
2065651712
            for i in 0..N {
315
                unsafe {
316
2065651712
                    let entry_ptr = data_ptr.add(i);
317
2065651712
                    if std::ptr::eq(*(*entry_ptr).next, nonexisting_value) {
318
342969344
                        *(*entry_ptr).next = std::ptr::null_mut();
319
342969344
                        let entry_ptr = NonNull::new_unchecked(entry_ptr);
320

            
321
342969344
                        if let Some(tail) = chunk_tail {
322
341956978
                            *(*tail.as_ptr()).next = entry_ptr.as_ptr();
323
341956978
                            chunk_tail = Some(entry_ptr);
324
341956978
                            chunk_len += 1;
325
341956978
                        } else {
326
1012366
                            chunk_head = Some(entry_ptr);
327
1012366
                            chunk_tail = Some(entry_ptr);
328
1012366
                            chunk_len = 1;
329
1012366
                        }
330

            
331
342969344
                        if chunk_len == FREE_LIST_CHUNK_SIZE {
332
193
                            rebuilt_chunk_heads.push(chunk_head.expect("chunk head set"));
333
193
                            chunk_head = None;
334
193
                            chunk_tail = None;
335
193
                            chunk_len = 0;
336
342969151
                        }
337
1722682368
                    }
338
                }
339
            }
340
        }
341

            
342
3884170
        if let Some(head) = chunk_head {
343
1012173
            rebuilt_chunk_heads.push(head);
344
2872097
        }
345

            
346
3884170
        guard.free_chunks.extend(rebuilt_chunk_heads);
347

            
348
3884170
        drop(guard);
349
3884170
        (removed, free_size)
350
3884170
    }
351

            
352
    /// Returns an iterator over the blocks.
353
    ///
354
    /// The caller must pass the already-acquired guard to avoid a deadlock.
355
7768340
    fn iter_blocks<'a>(guard: &'a MutexGuard<'_, BlockList<T, N>>) -> BlockIter<'a, T, N> {
356
7768340
        BlockIter {
357
7768340
            current: guard.head_block,
358
7768340
            _marker: PhantomData,
359
7768340
        }
360
7768340
    }
361
}
362

            
363
/// Per-thread state for allocation: current block and bump offset.
364
/// This eliminates contention in the hot path.
365
struct ThreadLocalAllocState<T, const N: usize> {
366
    /// The block currently being bump-allocated from.
367
    current_block: Cell<*mut Block<T, N>>,
368

            
369
    /// Bump offset within `current_block`. N or greater means the block is full.
370
    bump_offset: Cell<usize>,
371

            
372
    /// Thread-local free list (only popped from during allocation).
373
    free: FreeList<Entry<T>>,
374
}
375

            
376
impl<T, const N: usize> ThreadLocalAllocState<T, N> {
377
4928
    fn new() -> Self {
378
4928
        Self {
379
4928
            current_block: Cell::new(std::ptr::null_mut()),
380
4928
            bump_offset: Cell::new(N),
381
4928
            free: FreeList::new(),
382
4928
        }
383
4928
    }
384
}
385

            
386
unsafe impl<T: Send, const N: usize> Send for ThreadLocalAllocState<T, N> {}
387

            
388
/// Implementing this trait for a type `T` asserts that the special sentinel
389
/// never occurs as a valid entry.
390
///
391
/// # Safety
392
///
393
/// Marker trait asserting that the sentinel value used by [`BlockAllocator`]—
394
/// specifically, a pointer value where all bytes are set to `0xFF` (i.e.,
395
/// `usize::MAX` cast to `*mut Entry<T>`)—can never appear as the first
396
/// `size_of::<*mut _>()` bytes of any valid value of `T`.
397
pub unsafe trait BlockAllocatorSafe {}
398

            
399
/// Sentinel value used to identify entries that are not on the freelist.
400
const NONEXISTING_VALUE: usize = usize::MAX;
401

            
402
/// The [BlockAllocator] is thread-safe.
403
unsafe impl<T: Send, const N: usize> Send for BlockAllocator<T, N> {}
404
unsafe impl<T: Send, const N: usize> Sync for BlockAllocator<T, N> {}
405

            
406
/// `AllocBlock` implements the [`Allocator`] trait using the underlying [`BlockAllocator`].
407
pub struct AllocBlock<T: Send, const N: usize> {
408
    block_allocator: BlockAllocator<T, N>,
409
}
410

            
411
impl<T: Send, const N: usize> Default for AllocBlock<T, N> {
412
    fn default() -> Self {
413
        Self::new()
414
    }
415
}
416

            
417
impl<T: Send, const N: usize> AllocBlock<T, N> {
418
    /// Creates a new `AllocBlock`.
419
9590
    pub fn new() -> Self {
420
9590
        Self {
421
9590
            block_allocator: BlockAllocator::new(),
422
9590
        }
423
9590
    }
424

            
425
    /// Removes free blocks from the underlying block allocator, see [`BlockAllocator::remove_free_blocks`].
426
3884070
    pub fn remove_free_blocks(&mut self) -> usize
427
3884070
    where
428
3884070
        T: BlockAllocatorSafe,
429
    {
430
3884070
        self.block_allocator.remove_free_blocks().0
431
3884070
    }
432
}
433

            
434
unsafe impl<T: Send, const N: usize> Allocator for AllocBlock<T, N> {
435
14022052
    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
436
14022052
        debug_assert_eq!(
437
            layout,
438
14022052
            Layout::new::<T>(),
439
            "The requested layout should match the type T"
440
        );
441

            
442
14022052
        let ptr = self.block_allocator.allocate_object()?;
443

            
444
        // Convert NonNull<T> to NonNull<[u8]> with the correct size
445
14022052
        let byte_ptr = ptr.cast::<u8>();
446
14022052
        let slice_ptr = NonNull::slice_from_raw_parts(byte_ptr, std::mem::size_of::<T>());
447

            
448
14022052
        Ok(slice_ptr)
449
14022052
    }
450

            
451
13779610
    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
452
13779610
        debug_assert_eq!(
453
            layout,
454
13779610
            Layout::new::<T>(),
455
            "The requested layout should match the type T"
456
        );
457
13779610
        self.block_allocator.deallocate_object(ptr.cast::<T>());
458
13779610
    }
459
}
460

            
461
union Entry<T> {
462
    /// Stores the actual element.
463
    data: ManuallyDrop<T>,
464

            
465
    /// If the element is free, this points to the next entry in the freelist, or null if this is the last entry.
466
    next: ManuallyDrop<*mut Entry<T>>,
467
}
468

            
469
// Safety: `Entry<T>` stores a single intrusive next-pointer in `next` used only
470
// while the slot is on the freelist.
471
unsafe impl<T> FreeListEntry for Entry<T> {
472
356664415
    unsafe fn get_next(ptr: *mut Self) -> *mut Self {
473
        // Safety: caller ensures `ptr` is a valid freelist node.
474
356664415
        unsafe { *(*ptr).next }
475
356664415
    }
476

            
477
13830029
    unsafe fn set_next(ptr: *mut Self, next: *mut Self) {
478
        // Safety: caller ensures `ptr` is a valid freelist node.
479
13830029
        unsafe {
480
13830029
            *(*ptr).next = next;
481
13830029
        }
482
13830029
    }
483
}
484

            
485
/// An iterator over the blocks in the block allocator.
486
struct BlockIter<'a, T, const N: usize> {
487
    current: Option<NonNull<Block<T, N>>>,
488
    _marker: PhantomData<&'a BlockList<T, N>>,
489
}
490

            
491
impl<'a, T, const N: usize> Iterator for BlockIter<'a, T, N> {
492
    type Item = NonNull<Block<T, N>>;
493

            
494
11809016
    fn next(&mut self) -> Option<Self::Item> {
495
11809016
        let current_ptr = self.current?;
496

            
497
        // Move to the next block for the next iteration.
498
4040676
        self.current = unsafe { (*current_ptr.as_ptr()).next };
499

            
500
4040676
        Some(current_ptr)
501
11809016
    }
502
}
503

            
504
/// We maintain a list of blocks that store N elements each.
505
struct Block<T, const N: usize> {
506
    /// Wrapped in UnsafeCell to avoid Stacked Borrows invalidation of
507
    /// outstanding pointers during subsequent allocations from the same block.
508
    data: UnsafeCell<[Entry<T>; N]>,
509

            
510
    /// Pointer to the next block (raw to avoid Box noalias).
511
    next: Option<NonNull<Block<T, N>>>,
512
}
513

            
514
impl<T, const N: usize> Block<T, N> {
515
8954
    fn new() -> Self {
516
        Self {
517
8954
            data: UnsafeCell::new(array::from_fn(|_i| Entry {
518
5940928
                next: ManuallyDrop::new(std::ptr::null_mut()),
519
5940928
            })),
520
8954
            next: None,
521
        }
522
8954
    }
523
}
524

            
525
impl<T, const N: usize> Drop for Block<T, N> {
526
3254
    fn drop(&mut self) {
527
        // Iteratively drop the list to avoid stack overflow on long lists.
528
3254
        let mut current = self.next.take();
529
6407
        while let Some(block_ptr) = current {
530
3153
            let mut block = unsafe { Box::from_raw(block_ptr.as_ptr()) };
531
3153
            current = block.next.take();
532
3153
        }
533
3254
    }
534
}
535

            
536
#[cfg(test)]
537
mod tests {
538
    use std::ptr::NonNull;
539
    use std::sync::Arc;
540

            
541
    use rand::RngExt;
542

            
543
    use merc_utilities::random_test;
544

            
545
    use super::BlockAllocator;
546
    use super::BlockAllocatorSafe;
547

            
548
    // In practice u64 is used only in tests; real clients must audit their types.
549
    unsafe impl BlockAllocatorSafe for usize {}
550

            
551
    #[test]
552
    #[cfg_attr(miri, ignore)]
553
1
    fn test_block_allocator() {
554
100
        random_test(100, |rng| {
555
100
            let mut allocator: BlockAllocator<usize, 32> = BlockAllocator::new();
556

            
557
            // Allocate 1000 elements and keep track of ptr to value mapping.
558
100
            let mut allocated: Vec<(NonNull<usize>, usize)> = Vec::new();
559
100000
            for _ in 0..1000 {
560
100000
                let ptr = allocator.allocate_object().unwrap();
561
100000
                let value: usize = rng.random_range(0..=usize::MAX - 1);
562
100000
                unsafe {
563
100000
                    ptr.as_ptr().write(value);
564
100000
                }
565
100000
                allocated.push((ptr, value));
566
100000
            }
567

            
568
            // Deallocate a random subset and keep track of which entries remain live.
569
100
            let mut remaining = Vec::new();
570
100000
            for (ptr, value) in allocated {
571
100000
                if rng.random_bool(0.5) {
572
50119
                    allocator.deallocate_object(ptr);
573
50119
                } else {
574
49881
                    remaining.push((ptr, value));
575
49881
                }
576
            }
577

            
578
            // All remaining elements must still hold their original values.
579
49881
            for (ptr, expected) in &remaining {
580
                unsafe {
581
49881
                    assert_eq!(*ptr.as_ref(), *expected);
582
                }
583
            }
584

            
585
100
            let (removed, free_size) = allocator.remove_free_blocks();
586
100
            println!("{removed} removed, {free_size} free");
587

            
588
50000
            for _ in 0..500 {
589
50000
                let ptr = allocator.allocate_object().unwrap();
590
50000
                let value: usize = rng.random_range(0..=usize::MAX - 1);
591
50000
                unsafe {
592
50000
                    ptr.as_ptr().write(value);
593
50000
                }
594
50000
                remaining.push((ptr, value));
595
50000
            }
596

            
597
            // All remaining elements must have the correct values.
598
99881
            for (ptr, expected) in &remaining {
599
                unsafe {
600
99881
                    assert_eq!(*ptr.as_ref(), *expected);
601
                }
602
            }
603
100
        })
604
1
    }
605

            
606
    #[test]
607
    #[cfg_attr(miri, ignore)]
608
1
    fn test_block_allocator_parallel_freelist() {
609
1
        let block_allocator = Arc::new(BlockAllocator::<u32, 32>::new());
610

            
611
1
        let threads: Vec<_> = (0..=2)
612
3
            .map(|_| {
613
3
                let block_allocator = block_allocator.clone();
614

            
615
3
                std::thread::spawn(move || {
616
                    // Not sure if this could actually trigger the ABA problem,
617
                    // but this is only used to detect data races.
618
3
                    let mut ptrs = Vec::new();
619
300
                    for _ in 0..100 {
620
300
                        let ptr = block_allocator.allocate_object().unwrap();
621
300
                        unsafe {
622
300
                            ptr.as_ptr().write(42);
623
300
                        }
624
300
                        ptrs.push(ptr);
625
300
                    }
626

            
627
300
                    for ptr in ptrs {
628
                        unsafe {
629
300
                            assert_eq!(*ptr.as_ref(), 42);
630
                        }
631
300
                        block_allocator.deallocate_object(ptr);
632
                    }
633
3
                })
634
3
            })
635
1
            .collect();
636

            
637
3
        for thread in threads {
638
3
            thread.join().unwrap();
639
3
        }
640
1
    }
641
}