Skip to main content

rts_alloc/
allocator.rs

1use crate::free_stack::FreeStack;
2use crate::global_free_list::GlobalFreeList;
3use crate::header::{self, WorkerLocalListHeads, WorkerLocalListPartialFullHeads};
4use crate::linked_list_node::LinkedListNode;
5use crate::size_classes::{size_class, size_class_unchecked};
6use crate::slab_meta::SlabMeta;
7use crate::sync::{AtomicUsize, Ordering};
8use crate::worker_local_list::WorkerLocalList;
9use crate::{
10    error::Error,
11    header::Header,
12    index::{NULL_U32, NULL_USIZE},
13    size_classes::size_class_index,
14};
15use core::{marker::PhantomData, mem::offset_of, ptr::NonNull};
16use std::fs::File;
17use std::sync::Arc;
18
19pub struct Allocator {
20    base: AllocatorBase,
21    worker_index: u32,
22    // `Allocator` may be moved between threads, but it is not safe to share
23    // one instance concurrently.
24    _not_sync: PhantomData<core::cell::Cell<()>>,
25}
26
27pub struct FreeOnlyAllocator {
28    base: AllocatorBase,
29}
30
31struct MappedRegion {
32    header: NonNull<Header>,
33    file_size: usize,
34}
35
36impl Drop for MappedRegion {
37    fn drop(&mut self) {
38        // SAFETY: The mapped region was created by `map_file` and is valid until drop.
39        let _ = crate::memory_map::unmap_file(self.header.as_ptr().cast(), self.file_size);
40    }
41}
42
43// SAFETY: `MappedRegion` holds an immutable pointer and size for a shared
44// mapping. The backing memory is process-shared and thread-safe access is
45// enforced by allocator logic and atomics in shared metadata; transferring or
46// sharing this handle across threads does not violate aliasing or
47// thread-safety guarantees.
48unsafe impl Send for MappedRegion {}
49// SAFETY: See rationale above for `Send`.
50unsafe impl Sync for MappedRegion {}
51
52#[derive(Clone)]
53pub(crate) struct AllocatorBase {
54    region: Arc<MappedRegion>,
55    layout: CachedLayout,
56}
57
58#[derive(Clone, Copy)]
59struct CachedLayout {
60    num_slabs: u32,
61    num_workers: u32,
62    slab_size: u32,
63    slab_size_shift: u32,
64    free_list_elements_offset: u32,
65    slab_shared_meta_offset: u32,
66    slab_free_stacks_offset: u32,
67    slabs_offset: u32,
68}
69
70impl Allocator {
71    /// Create a new `Allocator` in the provided file with the given parameters.
72    /// `min_workers` is the minimum number of workers to support.
73    ///
74    /// # Safety
75    /// - `create` must only be called once for a given file. Subsequent calls
76    ///   with the same file must use `join`.
77    pub unsafe fn create(
78        file: &File,
79        file_size: usize,
80        min_workers: u32,
81        slab_size: u32,
82    ) -> Result<Self, Error> {
83        let header = crate::init::create(file, file_size, min_workers, slab_size)?;
84        // SAFETY:
85        // - `header` and `file_size` are trusted arguments from the above create call.
86        let base = unsafe { AllocatorBase::from_mapping(header, file_size) };
87        // SAFETY: `base.header()` points to a valid, initialized header.
88        let worker_index = match unsafe { claim_any_worker_index(base.header()) } {
89            Some(worker_index) => worker_index,
90            None => return Err(Error::NoAvailableWorkers),
91        };
92
93        Allocator::new(base, worker_index)
94    }
95
96    /// Join an existing allocator in the provided file.
97    /// Picks the first available worker slot.
98    ///
99    /// # Note
100    ///
101    /// Prefer [`Self::join_from_existing`] to re-use `mmap`s within the same
102    /// process.
103    pub fn join(file: &File) -> Result<Self, Error> {
104        let (header, file_size) = crate::init::join(file)?;
105        // SAFETY:
106        // - `header` and `file_size` are trusted arguments from the above join call.
107        let base = unsafe { AllocatorBase::from_mapping(header, file_size) };
108        // SAFETY: `base.header()` points to a valid, initialized header.
109        let worker_index = match unsafe { claim_any_worker_index(base.header()) } {
110            Some(worker_index) => worker_index,
111            None => return Err(Error::NoAvailableWorkers),
112        };
113
114        Allocator::new(base, worker_index)
115    }
116
117    /// Join an existing allocator using the same in-process mapping.
118    /// Picks the first available worker slot.
119    pub fn join_from_existing(existing: &Allocator) -> Result<Self, Error> {
120        Self::join_from_base(&existing.base)
121    }
122
123    /// Join an existing free-only allocator using the same in-process mapping.
124    /// Picks the first available worker slot.
125    pub fn join_from_existing_free_only(existing: &FreeOnlyAllocator) -> Result<Self, Error> {
126        Self::join_from_base(&existing.base)
127    }
128
129    /// Join using a shared [`AllocatorBase`].
130    /// Picks the first available worker slot.
131    fn join_from_base(base: &AllocatorBase) -> Result<Self, Error> {
132        // SAFETY: `base.header()` points to a valid, initialized header.
133        let worker_index = match unsafe { claim_any_worker_index(base.header()) } {
134            Some(worker_index) => worker_index,
135            None => return Err(Error::NoAvailableWorkers),
136        };
137        Allocator::new(base.clone(), worker_index)
138    }
139
140    /// Creates a new `Allocator` for the given worker index.
141    fn new(base: AllocatorBase, worker_index: u32) -> Result<Self, Error> {
142        if worker_index >= base.layout.num_workers {
143            return Err(Error::InvalidWorkerIndex);
144        }
145        Ok(Allocator {
146            base,
147            worker_index,
148            _not_sync: PhantomData,
149        })
150    }
151
152    pub(crate) fn base(&self) -> &AllocatorBase {
153        &self.base
154    }
155
156    pub(crate) fn worker_index(&self) -> u32 {
157        self.worker_index
158    }
159}
160
161unsafe impl Send for Allocator {}
162unsafe impl Send for FreeOnlyAllocator {}
163
164impl Drop for Allocator {
165    fn drop(&mut self) {
166        self.release_worker();
167    }
168}
169
170impl FreeOnlyAllocator {
171    /// Join an existing allocator in the provided file.
172    ///
173    /// # Note
174    ///
175    /// Prefer [`Self::join_from_existing`] to re-use `mmap`s within the same
176    /// process.
177    pub fn join(file: &File) -> Result<Self, Error> {
178        let (header, file_size) = crate::init::join(file)?;
179        // SAFETY:
180        // - `header` and `file_size` are trusted arguments from the above join call.
181        Ok(FreeOnlyAllocator {
182            base: unsafe { AllocatorBase::from_mapping(header, file_size) },
183        })
184    }
185
186    /// Join an existing allocator using the same in-process mapping.
187    pub fn join_from_existing(existing: &Allocator) -> Self {
188        Self::from_base(&existing.base)
189    }
190
191    /// Join an existing free-only allocator using the same in-process mapping.
192    pub fn join_from_existing_free_only(existing: &FreeOnlyAllocator) -> Self {
193        Self::from_base(&existing.base)
194    }
195
196    fn from_base(base: &AllocatorBase) -> Self {
197        Self { base: base.clone() }
198    }
199
200    pub(crate) fn base(&self) -> &AllocatorBase {
201        &self.base
202    }
203}
204
205impl Allocator {
206    fn release_worker(&self) {
207        self.worker_meta().claimed.store(0, Ordering::Release);
208    }
209
210    /// Allocates a block of memory of the given size.
211    /// If the size is larger than the maximum size class, returns `None`.
212    /// If the allocation fails, returns `None`.
213    pub fn allocate(&self, size: u32) -> Option<NonNull<u8>> {
214        // Explicitly reject zero-sized allocations.
215        if size == 0 {
216            return None;
217        }
218        let size_index = size_class_index(size)?;
219
220        // SAFETY: `size_index` is guaranteed to be valid by `size_class_index`.
221        let slab_index = unsafe { self.find_allocatable_slab_index(size_index) }?;
222        // SAFETY:
223        // - `slab_index` is guaranteed to be valid by `find_allocatable_slab_index`.
224        // - `size_index` is guaranteed to be valid by `size_class_index`.
225        unsafe { self.allocate_within_slab(slab_index, size_index) }
226    }
227
228    /// Try to find a suitable slab for allocation.
229    /// If a partial slab assigned to the worker is not found, then try to find
230    /// a slab from the global free list.
231    ///
232    /// # Safety
233    /// - The `size_index` must be a valid index for the size classes.
234    unsafe fn find_allocatable_slab_index(&self, size_index: usize) -> Option<u32> {
235        // SAFETY: `size_index` is guaranteed to be valid by the caller.
236        unsafe { self.worker_local_list_partial(size_index) }
237            .head()
238            .or_else(|| self.take_slab(size_index))
239    }
240
241    /// Attempt to allocate memory within a slab.
242    /// If the slab is full or the allocation otherwise fails, returns `None`.
243    ///
244    /// # Safety
245    /// - The `slab_index` must be a valid index for the slabs
246    /// - The `size_index` must be a valid index for the size classes.
247    unsafe fn allocate_within_slab(
248        &self,
249        slab_index: u32,
250        size_index: usize,
251    ) -> Option<NonNull<u8>> {
252        // SAFETY: The slab index is guaranteed to be valid by the caller.
253        let mut free_stack = unsafe { self.slab_free_stack(slab_index) };
254        let maybe_index_within_slab = free_stack.pop();
255
256        // If the slab is empty - remove it from the worker's partial list,
257        // and move it to the worker's full list.
258        if free_stack.is_empty() {
259            // SAFETY:
260            // - The `slab_index` is guaranteed to be valid by the caller.
261            // - The `size_index` is guaranteed to be valid by the caller.
262            unsafe {
263                self.worker_local_list_partial(size_index)
264                    .remove(slab_index);
265            }
266            // SAFETY:
267            // - The `slab_index` is guaranteed to be valid by the caller.
268            // - The `size_index` is guaranteed to be valid by the caller.
269            unsafe {
270                self.worker_local_list_full(size_index).push(slab_index);
271            }
272        }
273
274        maybe_index_within_slab.map(|index_within_slab| {
275            // SAFETY: The `slab_index` is guaranteed to be valid by the caller.
276            let slab = unsafe { self.slab(slab_index) };
277            // SAFETY: The `size_index` is guaranteed to be valid by the caller.
278            let size = unsafe { size_class_unchecked(size_index) };
279            self.worker_meta()
280                .outstanding_allocation_bytes
281                .fetch_add(size as u64, Ordering::Relaxed);
282            slab.byte_add(index_within_slab as usize * size as usize)
283        })
284    }
285
286    /// Attempt to take a slab from the global free list.
287    /// If the global free list is empty, returns `None`.
288    /// If the slab is successfully taken, it will be marked as assigned to the worker.
289    ///
290    /// # Safety
291    /// - The `size_index` must be a valid index for the size claasses.
292    unsafe fn take_slab(&self, size_index: usize) -> Option<u32> {
293        let slab_index = self.global_free_list().pop()?;
294
295        // SAFETY: The slab index is guaranteed to be valid by `pop`.
296        unsafe { self.slab_meta(slab_index).as_ref() }.assign(self.worker_index, size_index);
297        // SAFETY:
298        // - The slab index is guaranteed to be valid by `pop`.
299        // - The size index is guaranteed to be valid by the caller.
300        unsafe {
301            let slab_capacity = self.base.layout.slab_size / size_class_unchecked(size_index);
302            self.slab_free_stack(slab_index).reset(slab_capacity as u16);
303        };
304        // SAFETY: The size index is guaranteed to be valid by caller.
305        let mut worker_local_list = unsafe { self.worker_local_list_partial(size_index) };
306        // SAFETY: The slab index is guaranteed to be valid by `pop`.
307        unsafe { worker_local_list.push(slab_index) };
308        Some(slab_index)
309    }
310}
311
312impl Allocator {
313    /// Free a block of memory previously allocated by this allocator.
314    ///
315    /// # Safety
316    /// - The `ptr` must be a valid pointer to a block of memory allocated by this allocator.
317    /// - The `ptr` must not have been freed before.
318    pub unsafe fn free(&self, ptr: NonNull<u8>) {
319        // SAFETY: The pointer is assumed to be valid and allocated by this allocator.
320        let offset = unsafe { self.offset(ptr) };
321        self.free_offset(offset);
322    }
323
324    /// Free a block of memory previously allocated by this allocator.
325    ///
326    /// # Safety
327    /// - The `offset` must be a valid offset to a block of memory allocated by this allocator,
328    ///   i.e. an offset returned by [`Self::offset`].
329    /// - The `offset` must not have been freed before.
330    pub unsafe fn free_offset(&self, offset: usize) {
331        let allocation_indexes = self.find_allocation_indexes(offset);
332
333        // Check if the slab is assigned to this worker.
334        if self.worker_index
335            == unsafe { self.slab_meta(allocation_indexes.slab_index).as_ref() }
336                .assigned_worker
337                .load(Ordering::Acquire)
338        {
339            // SAFETY: The indexes came from a valid allocation offset and the
340            // ownership check above confirms its slab is local.
341            unsafe { self.free_local(allocation_indexes) };
342        } else {
343            self.remote_free(offset, allocation_indexes.slab_index);
344        }
345    }
346
347    /// Free an allocation known to be owned by this worker.
348    ///
349    /// # Safety
350    /// - `allocation_indexes` must identify a valid allocation in a slab owned
351    ///   by this worker.
352    pub(crate) unsafe fn free_local(&self, allocation_indexes: AllocationIndexes) {
353        // SAFETY: Guaranteed by the caller.
354        let (size_index, size) = unsafe { self.slab_size_class(allocation_indexes.slab_index) };
355        self.worker_meta()
356            .outstanding_allocation_bytes
357            .fetch_sub(size as u64, Ordering::Relaxed);
358        self.local_free_with_size_index(allocation_indexes, size_index);
359    }
360
361    fn local_free_with_size_index(&self, allocation_indexes: AllocationIndexes, size_index: usize) {
362        // SAFETY: The allocation indexes are guaranteed to be valid by the caller.
363        let (was_full, is_empty) = unsafe {
364            let mut free_stack = self.slab_free_stack(allocation_indexes.slab_index);
365            let was_full = free_stack.is_empty();
366            free_stack.push(allocation_indexes.index_within_slab);
367            // Names confusing:
368            // - When the **free** stack is empty, the slab is full of allocations.
369            // - When the **free** stack is full, the slab has no allocations available.
370            (was_full, free_stack.is_full())
371        };
372
373        match (was_full, is_empty) {
374            (true, true) => {
375                // The slab was full and is now empty - this cannot happen unless the slab
376                // size is equal to the size class.
377                unreachable!("slab can only contain one allocation - this is not allowed");
378            }
379            (true, false) => {
380                // The slab was full and is now partially full. It must be moved
381                // from the worker's full list to the worker's partial list.
382                // SAFETY: The allocation indexes are guaranteed to be valid by the caller.
383                unsafe {
384                    self.worker_local_list_full(size_index)
385                        .remove(allocation_indexes.slab_index);
386                }
387                // SAFETY: The allocation indexes are guaranteed to be valid by the caller.
388                unsafe {
389                    self.worker_local_list_partial(size_index)
390                        .push(allocation_indexes.slab_index);
391                }
392            }
393            (false, true) => {
394                // The slab was partially full and is now empty.
395                // It must be moved from the worker's partial list to the global free list.
396                // SAFETY: The allocation indexes are guaranteed to be valid by the caller.
397                unsafe {
398                    self.worker_local_list_partial(size_index)
399                        .remove(allocation_indexes.slab_index);
400                }
401                // SAFETY: The allocation indexes are guaranteed to be valid by the caller.
402                unsafe {
403                    self.slab_meta(allocation_indexes.slab_index)
404                        .as_ref()
405                        .assigned_worker
406                        .store(NULL_U32, Ordering::Release);
407                }
408                // SAFETY: The allocation indexes are guaranteed to be valid by the caller.
409                unsafe {
410                    self.global_free_list().push(allocation_indexes.slab_index);
411                }
412            }
413            (false, false) => {
414                // The slab was partially full and is still partially full.
415                // No action is needed, just return.
416            }
417        }
418    }
419
420    fn remote_free(&self, offset: usize, slab_index: u32) {
421        self.base.remote_free(offset, slab_index);
422    }
423
424    /// Find the offset given a pointer.
425    ///
426    /// # Safety
427    /// - The `ptr` must be a valid pointer in the allocator's address space.
428    pub unsafe fn offset(&self, ptr: NonNull<u8>) -> usize {
429        self.base.offset(ptr)
430    }
431
432    /// Return a ptr given a shareable offset - calculated by `offset`.
433    ///
434    /// # Safety
435    ///
436    /// - Caller must ensure the offset is valid for this allocator.
437    pub unsafe fn ptr_from_offset(&self, offset: usize) -> NonNull<u8> {
438        self.base.ptr_from_offset(offset)
439    }
440
441    /// Find the slab index and index within the slab for a given offset.
442    fn find_allocation_indexes(&self, offset: usize) -> AllocationIndexes {
443        self.base.find_allocation_indexes(offset)
444    }
445}
446
447impl FreeOnlyAllocator {
448    /// Free a block of memory previously allocated by this allocator.
449    ///
450    /// # Safety
451    /// - The `ptr` must be a valid pointer to a block of memory allocated by this allocator.
452    /// - The `ptr` must not have been freed before.
453    pub unsafe fn free(&self, ptr: NonNull<u8>) {
454        // SAFETY: The pointer is assumed to be valid and allocated by this allocator.
455        let offset = unsafe { self.offset(ptr) };
456        self.free_offset(offset);
457    }
458
459    /// Free a block of memory previously allocated by this allocator.
460    ///
461    /// # Safety
462    /// - The `offset` must be a valid offset to a block of memory allocated by this allocator,
463    ///   i.e. an offset returned by [`Self::offset`].
464    /// - The `offset` must not have been freed before.
465    pub unsafe fn free_offset(&self, offset: usize) {
466        let allocation_indexes = self.find_allocation_indexes(offset);
467        self.base.remote_free(offset, allocation_indexes.slab_index);
468    }
469
470    /// Find the offset given a pointer.
471    ///
472    /// # Safety
473    /// - The `ptr` must be a valid pointer in the allocator's address space.
474    pub unsafe fn offset(&self, ptr: NonNull<u8>) -> usize {
475        self.base.offset(ptr)
476    }
477
478    /// Return a ptr given a shareable offset - calculated by `offset`.
479    ///
480    /// # Safety
481    ///
482    /// - Caller must ensure the offset is valid for this allocator.
483    pub unsafe fn ptr_from_offset(&self, offset: usize) -> NonNull<u8> {
484        self.base.ptr_from_offset(offset)
485    }
486
487    /// Find the slab index and index within the slab for a given offset.
488    fn find_allocation_indexes(&self, offset: usize) -> AllocationIndexes {
489        self.base.find_allocation_indexes(offset)
490    }
491}
492
493impl AllocatorBase {
494    /// # Safety
495    /// - `header` must be a valid pointer to an initialized mapping of `file_size` bytes.
496    /// - `file_size` must be the size of the mapping.
497    unsafe fn from_mapping(header: NonNull<Header>, file_size: usize) -> Self {
498        let layout = {
499            // SAFETY: The header is assumed to be valid and initialized by the caller.
500            let header = unsafe { header.as_ref() };
501            CachedLayout {
502                num_slabs: header.num_slabs,
503                num_workers: header.num_workers,
504                slab_size: header.slab_size,
505                slab_size_shift: header.slab_size.trailing_zeros(),
506                free_list_elements_offset: header.free_list_elements_offset,
507                slab_shared_meta_offset: header.slab_shared_meta_offset,
508                slab_free_stacks_offset: header.slab_free_stacks_offset,
509                slabs_offset: header.slabs_offset,
510            }
511        };
512        Self {
513            region: Arc::new(MappedRegion { header, file_size }),
514            layout,
515        }
516    }
517
518    #[inline]
519    fn header(&self) -> NonNull<Header> {
520        self.region.header
521    }
522
523    /// Find the offset given a pointer.
524    ///
525    /// # Safety
526    /// - The `ptr` must be a valid pointer in the allocator's address space.
527    unsafe fn offset(&self, ptr: NonNull<u8>) -> usize {
528        ptr.byte_offset_from(self.header()) as usize
529    }
530
531    /// Return a ptr given a shareable offset - calculated by `offset`.
532    ///
533    /// # Safety
534    ///
535    /// - Caller must ensure the offset is valid for this allocator.
536    unsafe fn ptr_from_offset(&self, offset: usize) -> NonNull<u8> {
537        unsafe { self.header().byte_add(offset) }.cast()
538    }
539
540    /// Find the slab index and index within the slab for a given offset.
541    fn find_allocation_indexes(&self, offset: usize) -> AllocationIndexes {
542        let (slab_index, offset_within_slab) = {
543            assert!(offset >= self.layout.slabs_offset as usize);
544            let offset_from_slab_start = offset.wrapping_sub(self.layout.slabs_offset as usize);
545            let slab_index = (offset_from_slab_start >> self.layout.slab_size_shift) as u32;
546            assert!(
547                slab_index < self.layout.num_slabs,
548                "slab index out of bounds"
549            );
550
551            let offset_within_slab =
552                Self::offset_within_slab(self.layout.slab_size, offset_from_slab_start);
553
554            (slab_index, offset_within_slab)
555        };
556
557        let index_within_slab = {
558            // SAFETY: The slab index is guaranteed to be valid by the above calculations.
559            let size_class_index = unsafe { self.slab_meta(slab_index).as_ref() }
560                .size_class_index
561                .load(Ordering::Acquire);
562            let size_class = size_class(size_class_index);
563            (offset_within_slab >> size_class.trailing_zeros()) as u16
564        };
565
566        AllocationIndexes {
567            slab_index,
568            index_within_slab,
569        }
570    }
571
572    /// Pushes an allocation offset onto the owning worker's remote-free list.
573    fn remote_free(&self, offset: usize, slab_index: u32) {
574        debug_assert_ne!(offset, NULL_USIZE);
575
576        // SAFETY: The slab index is guaranteed to be valid by the caller.
577        let slab_meta = unsafe { self.slab_meta(slab_index).as_ref() };
578        let worker_index = slab_meta.assigned_worker.load(Ordering::Acquire);
579        debug_assert!(worker_index < self.layout.num_workers);
580        if worker_index >= self.layout.num_workers {
581            return;
582        }
583
584        // SAFETY: `offset` is the valid, uniquely freed allocation being
585        // published, and `worker_index` was read from its slab metadata.
586        unsafe { self.publish_remote_free_chain(worker_index, offset, offset) };
587    }
588
589    /// Return the indexes and worker assigned to the allocation at `offset`.
590    ///
591    /// # Safety
592    /// - `offset` must refer to a valid allocation owned by this allocator.
593    pub(crate) unsafe fn allocation_indexes_and_assigned_worker(
594        &self,
595        offset: usize,
596    ) -> Option<(AllocationIndexes, u32)> {
597        let allocation_indexes = self.find_allocation_indexes(offset);
598        // SAFETY: `find_allocation_indexes` guarantees a valid slab index.
599        let slab_meta = unsafe { self.slab_meta(allocation_indexes.slab_index).as_ref() };
600        let worker_index = slab_meta.assigned_worker.load(Ordering::Acquire);
601        debug_assert!(worker_index < self.layout.num_workers);
602        if worker_index >= self.layout.num_workers {
603            return None;
604        }
605        Some((allocation_indexes, worker_index))
606    }
607
608    /// Set the intrusive next link stored within a remotely freed allocation.
609    ///
610    /// # Safety
611    /// - `offset` must refer to a valid, uniquely freed allocation suitable for
612    ///   storing an [`AtomicUsize`].
613    pub(crate) unsafe fn set_remote_free_next(&self, offset: usize, next: usize) {
614        debug_assert_ne!(offset, NULL_USIZE);
615        // SAFETY: Guaranteed by the caller.
616        let remote_free_node: &AtomicUsize =
617            unsafe { self.ptr_from_offset(offset).cast().as_ref() };
618        remote_free_node.store(next, Ordering::Release);
619    }
620
621    /// Publish a private chain to a worker's remote-free stack.
622    ///
623    /// # Safety
624    /// - `worker_index` must be the worker that owns the allocations in the chain.
625    /// - `head` and `tail` must be valid, uniquely freed allocation offsets whose
626    ///   intrusive links form a private chain.
627    pub(crate) unsafe fn publish_remote_free_chain(
628        &self,
629        worker_index: u32,
630        head: usize,
631        tail: usize,
632    ) {
633        debug_assert_ne!(head, NULL_USIZE);
634        debug_assert_ne!(tail, NULL_USIZE);
635        debug_assert!(worker_index < self.layout.num_workers);
636        if worker_index >= self.layout.num_workers {
637            return;
638        }
639
640        // SAFETY: The worker index is checked against the layout above.
641        let worker_meta = unsafe { worker_meta_ptr(self.header(), worker_index).as_ref() };
642        let remote_free_head = &worker_meta.remote_free_head;
643
644        let mut current_head = remote_free_head.load(Ordering::Acquire);
645        loop {
646            // SAFETY: The caller guarantees that `tail` is a valid, uniquely freed
647            // allocation offset.
648            unsafe { self.set_remote_free_next(tail, current_head) };
649            match remote_free_head.compare_exchange(
650                current_head,
651                head,
652                Ordering::AcqRel,
653                Ordering::Acquire,
654            ) {
655                Ok(_) => return,
656                Err(next_head) => current_head = next_head,
657            }
658        }
659    }
660
661    /// Return offset within a slab.
662    ///
663    const fn offset_within_slab(slab_size: u32, offset_from_slab_start: usize) -> u32 {
664        debug_assert!(slab_size.is_power_of_two());
665        (offset_from_slab_start & (slab_size as usize - 1)) as u32
666    }
667
668    /// Returns a pointer to the slab meta for the given slab index.
669    ///
670    /// # Safety
671    /// - The `slab_index` must be a valid index for the slabs.
672    unsafe fn slab_meta(&self, slab_index: u32) -> NonNull<SlabMeta> {
673        let offset = self.layout.slab_shared_meta_offset;
674        // SAFETY: The header is guaranteed to be valid and initialized.
675        let slab_metas = unsafe { self.header().byte_add(offset as usize).cast::<SlabMeta>() };
676        // SAFETY: The `slab_index` is guaranteed to be valid by the caller.
677        unsafe { slab_metas.add(slab_index as usize) }
678    }
679
680    /// Return a pointer to a slab.
681    ///
682    /// # Safety
683    /// - The `slab_index` must be a valid index for the slabs.
684    unsafe fn slab(&self, slab_index: u32) -> NonNull<u8> {
685        // SAFETY: The header is guaranteed to be valid and initialized.
686        // The slabs are laid out sequentially after the free stacks.
687        unsafe {
688            self.header()
689                .byte_add(self.layout.slabs_offset as usize)
690                .byte_add(slab_index as usize * self.layout.slab_size as usize)
691                .cast()
692        }
693    }
694
695    fn free_list_elements(&self) -> &[LinkedListNode] {
696        let offset = self.layout.free_list_elements_offset;
697        // SAFETY:
698        // - The header is guaranteed to be valid and initialized.
699        // - The pointer is aligned for `LinkedListNode` (guaranteed by layout).
700        // - The pointer is valid for `num_slabs` contiguous `LinkedListNode` elements.
701        unsafe {
702            core::slice::from_raw_parts(
703                self.header()
704                    .byte_add(offset as usize)
705                    .cast::<LinkedListNode>()
706                    .as_ptr(),
707                self.layout.num_slabs as usize,
708            )
709        }
710    }
711}
712
713impl Allocator {
714    pub fn outstanding_allocation_bytes(&self) -> u64 {
715        self.worker_meta()
716            .outstanding_allocation_bytes
717            .load(Ordering::Relaxed)
718    }
719
720    /// Frees all remotely freed items queued for this worker.
721    pub fn clean_remote_frees(&self) {
722        let mut offset = self
723            .worker_meta()
724            .remote_free_head
725            .swap(NULL_USIZE, Ordering::AcqRel);
726
727        while offset != NULL_USIZE {
728            // SAFETY: Remote free entries are allocation offsets pushed by `remote_free`.
729            let remote_free_node: &AtomicUsize =
730                unsafe { self.base.ptr_from_offset(offset).cast().as_ref() };
731            let next_offset = remote_free_node.load(Ordering::Acquire);
732            let allocation_indexes = self.find_allocation_indexes(offset);
733            // SAFETY: Allocation indexes come from a valid allocation offset.
734            let (size_index, size) = unsafe { self.slab_size_class(allocation_indexes.slab_index) };
735            self.local_free_with_size_index(allocation_indexes, size_index);
736            self.worker_meta()
737                .outstanding_allocation_bytes
738                .fetch_sub(size as u64, Ordering::Relaxed);
739            offset = next_offset;
740        }
741    }
742}
743
744impl Allocator {
745    /// Returns a slice of the free list elements in allocator.
746    fn free_list_elements(&self) -> &[LinkedListNode] {
747        self.base.free_list_elements()
748    }
749
750    /// Returns a `GlobalFreeList` to interact with the global free list.
751    fn global_free_list<'a>(&'a self) -> GlobalFreeList<'a> {
752        // SAFETY: The header is assumed to be valid and initialized.
753        let header = unsafe { self.base.header().as_ref() };
754        let head = &header.global_free_list_head;
755        let list = self.free_list_elements();
756        GlobalFreeList::new(head, list)
757    }
758
759    /// Returns a `WorkerLocalList` for the current worker to interact with its
760    /// local free list of partially full slabs.
761    ///
762    /// # Safety
763    /// - The `size_index` must be a valid index for the size classes.
764    unsafe fn worker_local_list_partial<'a>(&'a self, size_index: usize) -> WorkerLocalList<'a> {
765        let head = &self.worker_head(size_index).partial;
766        let list = self.free_list_elements();
767        WorkerLocalList::new(head, list)
768    }
769
770    /// Returns a `WorkerLocalList` for the current worker to interact with its
771    /// local free list of full slabs.
772    ///
773    /// # Safety
774    /// - The `size_index` must be a valid index for the size classes.
775    unsafe fn worker_local_list_full<'a>(&'a self, size_index: usize) -> WorkerLocalList<'a> {
776        let head = &self.worker_head(size_index).full;
777        let list = self.free_list_elements();
778        WorkerLocalList::new(head, list)
779    }
780
781    fn worker_meta(&self) -> &WorkerLocalListHeads {
782        // SAFETY: The worker index is guaranteed to be valid by the constructor.
783        unsafe { worker_meta_ptr(self.base.header(), self.worker_index).as_ref() }
784    }
785
786    fn worker_head(&self, size_index: usize) -> &WorkerLocalListPartialFullHeads {
787        &self.worker_meta().heads[size_index]
788    }
789
790    /// Returns the slab's assigned size class index and class size in bytes.
791    ///
792    /// # Safety
793    /// - `slab_index` must be a valid slab index.
794    unsafe fn slab_size_class(&self, slab_index: u32) -> (usize, u32) {
795        let size_index = unsafe { self.slab_meta(slab_index).as_ref() }
796            .size_class_index
797            .load(Ordering::Relaxed);
798        let size = size_class(size_index);
799        (size_index, size)
800    }
801
802    /// Returns a pointer to the slab meta for the given slab index.
803    ///
804    /// # Safety
805    /// - The `slab_index` must be a valid index for the slabs.
806    unsafe fn slab_meta(&self, slab_index: u32) -> NonNull<SlabMeta> {
807        self.base.slab_meta(slab_index)
808    }
809
810    /// Return a mutable reference to a free stack for the given slab index.
811    ///
812    /// # Safety
813    /// - The `slab_index` must be a valid index for the slabs.
814    unsafe fn slab_free_stack<'a>(&'a self, slab_index: u32) -> FreeStack<'a> {
815        let free_stack_size = header::layout::single_free_stack_size(self.base.layout.slab_size);
816
817        // SAFETY: The `FreeStack` layout is guaranteed to have enough room
818        // for top, capacity, and the trailing stack.
819        let mut top = unsafe {
820            self.base
821                .header()
822                .byte_add(self.base.layout.slab_free_stacks_offset as usize)
823                .byte_add(slab_index as usize * free_stack_size)
824                .cast()
825        };
826        let mut capacity = unsafe { top.add(1) };
827        let trailing_stack = unsafe { capacity.add(1) };
828        unsafe { FreeStack::new(top.as_mut(), capacity.as_mut(), trailing_stack) }
829    }
830
831    /// Return a pointer to a slab.
832    ///
833    /// # Safety
834    /// - The `slab_index` must be a valid index for the slabs.
835    unsafe fn slab(&self, slab_index: u32) -> NonNull<u8> {
836        self.base.slab(slab_index)
837    }
838}
839
840unsafe fn worker_meta_ptr(
841    header: NonNull<Header>,
842    worker_index: u32,
843) -> NonNull<WorkerLocalListHeads> {
844    let all_workers_heads = unsafe {
845        header
846            .byte_add(offset_of!(Header, worker_local_list_heads))
847            .cast::<WorkerLocalListHeads>()
848    };
849    // SAFETY: The caller guarantees the worker index is in range.
850    unsafe { all_workers_heads.add(worker_index as usize) }
851}
852
853unsafe fn claim_any_worker_index(header: NonNull<Header>) -> Option<u32> {
854    let num_workers = unsafe { header.as_ref() }.num_workers;
855    for worker_index in 0..num_workers {
856        let claimed = unsafe { &worker_meta_ptr(header, worker_index).as_ref().claimed };
857        if claimed
858            .compare_exchange(0, 1, Ordering::AcqRel, Ordering::Acquire)
859            .is_ok()
860        {
861            return Some(worker_index);
862        }
863    }
864    None
865}
866
867pub(crate) struct AllocationIndexes {
868    slab_index: u32,
869    index_within_slab: u16,
870}
871
872#[cfg(test)]
873mod tests {
874    use super::*;
875    use crate::size_classes::{MAX_SIZE, NUM_SIZE_CLASSES, SIZE_CLASSES};
876
877    const TEST_BUFFER_SIZE: usize = 64 * 1024 * 1024; // 64 MiB
878
879    fn create_temp_shmem_file() -> Result<File, Error> {
880        use std::fs::OpenOptions;
881        use std::sync::atomic::{AtomicU64, Ordering};
882
883        static COUNTER: AtomicU64 = AtomicU64::new(0);
884        let temp_dir = std::env::temp_dir();
885        let n = COUNTER.fetch_add(1, Ordering::Relaxed);
886        let path = temp_dir.join(format!("rts-alloc-{n}.tmp"));
887
888        let mut open_options = OpenOptions::new();
889        open_options.read(true).write(true).create_new(true);
890
891        #[cfg(windows)]
892        {
893            use std::os::windows::fs::OpenOptionsExt;
894            use windows_sys::Win32::Storage::FileSystem::{
895                FILE_ATTRIBUTE_TEMPORARY, FILE_FLAG_DELETE_ON_CLOSE,
896            };
897
898            open_options
899                .attributes(FILE_ATTRIBUTE_TEMPORARY)
900                .custom_flags(FILE_FLAG_DELETE_ON_CLOSE);
901        }
902
903        let open_result = open_options.open(&path);
904
905        match open_result {
906            Ok(file) => {
907                #[cfg(unix)]
908                {
909                    std::fs::remove_file(&path)?;
910                }
911                Ok(file)
912            }
913            Err(err) => Err(Error::IoError(err)),
914        }
915    }
916
917    fn initialize_for_test(slab_size: u32, num_workers: u32) -> (File, Allocator) {
918        let file = create_temp_shmem_file().unwrap();
919        // SAFETY: Test helper creates allocator from a fresh temp shared-memory file.
920        let allocator =
921            unsafe { Allocator::create(&file, TEST_BUFFER_SIZE, num_workers, slab_size).unwrap() };
922        (file, allocator)
923    }
924
925    fn remote_free_stack(allocator: &Allocator) -> Vec<usize> {
926        let mut free_offsets = Vec::new();
927        let mut offset = allocator
928            .worker_meta()
929            .remote_free_head
930            .load(Ordering::Acquire);
931        while offset != NULL_USIZE {
932            free_offsets.push(offset);
933            let remote_free_node: &AtomicUsize =
934                unsafe { allocator.base.ptr_from_offset(offset).cast().as_ref() };
935            offset = remote_free_node.load(Ordering::Acquire);
936        }
937        free_offsets
938    }
939
940    #[test]
941    fn test_allocator() {
942        let slab_size = 65536; // 64 KiB
943        let num_workers = 4;
944        let (_file, allocator) = initialize_for_test(slab_size, num_workers);
945        assert_eq!(allocator.outstanding_allocation_bytes(), 0);
946
947        let mut allocations = vec![];
948        let mut total_allocated_bytes = 0u64;
949
950        assert!(allocator.allocate(0).is_none());
951        for class_size in SIZE_CLASSES[..NUM_SIZE_CLASSES - 1].iter() {
952            for size in [class_size - 1, *class_size, class_size + 1] {
953                allocations.push(allocator.allocate(size).unwrap());
954                total_allocated_bytes += size_class_index(size)
955                    .map(|i| size_class(i) as u64)
956                    .unwrap();
957            }
958        }
959        for size in [MAX_SIZE - 1, MAX_SIZE] {
960            allocations.push(allocator.allocate(size).unwrap());
961            total_allocated_bytes += size_class_index(size)
962                .map(|i| size_class(i) as u64)
963                .unwrap();
964        }
965        assert_eq!(
966            allocator.outstanding_allocation_bytes(),
967            total_allocated_bytes
968        );
969        assert!(allocator.allocate(MAX_SIZE + 1).is_none());
970
971        // The worker should have local lists for all size classes.
972        for size_index in 0..NUM_SIZE_CLASSES {
973            // SAFETY: The size index is guaranteed to be valid by the loop.
974            let worker_local_list = unsafe { allocator.worker_local_list_partial(size_index) };
975            assert!(worker_local_list.head().is_some());
976        }
977
978        for ptr in allocations {
979            // SAFETY: ptr is valid allocation from the allocator.
980            unsafe {
981                allocator.free(ptr);
982            }
983        }
984        assert_eq!(allocator.outstanding_allocation_bytes(), 0);
985
986        // The worker local lists should be empty after freeing.
987        for size_index in 0..NUM_SIZE_CLASSES {
988            // SAFETY: The size index is guaranteed to be valid by the loop.
989            let worker_local_list = unsafe { allocator.worker_local_list_partial(size_index) };
990            assert_eq!(worker_local_list.head(), None);
991        }
992    }
993
994    #[test]
995    fn test_slab_list_transitions() {
996        let slab_size = 65536; // 64 KiB
997        let num_workers = 4;
998        let (_file, allocator) = initialize_for_test(slab_size, num_workers);
999
1000        let allocation_size = 2048;
1001        let size_index = size_class_index(allocation_size).unwrap();
1002        let allocations_per_slab = slab_size / allocation_size;
1003
1004        fn check_worker_list_expectations(
1005            allocator: &Allocator,
1006            size_index: usize,
1007            expect_partial: bool,
1008            expect_full: bool,
1009        ) {
1010            unsafe {
1011                let partial_list = allocator.worker_local_list_partial(size_index);
1012                assert_eq!(
1013                    partial_list.head().is_some(),
1014                    expect_partial,
1015                    "{:?}",
1016                    partial_list.head()
1017                );
1018
1019                let full_list = allocator.worker_local_list_full(size_index);
1020                assert_eq!(
1021                    full_list.head().is_some(),
1022                    expect_full,
1023                    "{:?}",
1024                    full_list.head()
1025                );
1026            }
1027        }
1028
1029        // The parital list and full list should begin empty.
1030        check_worker_list_expectations(&allocator, size_index, false, false);
1031
1032        let mut first_slab_allocations = vec![];
1033        for _ in 0..allocations_per_slab - 1 {
1034            first_slab_allocations.push(allocator.allocate(allocation_size).unwrap());
1035        }
1036
1037        // The first slab should be partially full and the full list empty.
1038        check_worker_list_expectations(&allocator, size_index, true, false);
1039
1040        // Allocate one more to fill the slab.
1041        first_slab_allocations.push(allocator.allocate(allocation_size).unwrap());
1042
1043        // The first slab should now be full and moved to the full list.
1044        check_worker_list_expectations(&allocator, size_index, false, true);
1045
1046        // Allocating again will give a new slab, which will be partially full.
1047        let second_slab_allocation = allocator.allocate(allocation_size).unwrap();
1048
1049        // The second slab should be partially full and the first slab in the full list.
1050        check_worker_list_expectations(&allocator, size_index, true, true);
1051
1052        let mut first_slab_allocations = first_slab_allocations.drain(..);
1053        unsafe {
1054            allocator.free(first_slab_allocations.next().unwrap());
1055        }
1056        // Both slabs should be partially full, and none are full.
1057        check_worker_list_expectations(&allocator, size_index, true, false);
1058
1059        // Free the first slab allocation.
1060        for ptr in first_slab_allocations {
1061            unsafe {
1062                allocator.free(ptr);
1063            }
1064        }
1065        // The first slab is now empty and should be moved to the global free list,
1066        // but the second slab is still partially full.
1067        check_worker_list_expectations(&allocator, size_index, true, false);
1068
1069        // Free the second slab allocation.
1070        unsafe {
1071            allocator.free(second_slab_allocation);
1072        }
1073        // Both slabs should now be empty and moved to the global free list.
1074        check_worker_list_expectations(&allocator, size_index, false, false);
1075    }
1076
1077    #[test]
1078    fn test_out_of_slabs() {
1079        let slab_size = 65536; // 64 KiB
1080        let num_workers = 4;
1081        let (_file, allocator) = initialize_for_test(slab_size, num_workers);
1082
1083        for index in 0..allocator.base.layout.num_slabs {
1084            let slab_index = unsafe { allocator.take_slab(0) }.unwrap();
1085            assert_eq!(slab_index, index);
1086        }
1087        // The next slab allocation should fail, as all slabs are taken.
1088        assert!(unsafe { allocator.take_slab(0) }.is_none());
1089    }
1090
1091    #[test]
1092    fn test_remote_free_lists() {
1093        let slab_size = 65536; // 64 KiB
1094        let num_workers = 4;
1095        let (file, allocator_0) = initialize_for_test(slab_size, num_workers);
1096        let file_for_join = file.try_clone().unwrap();
1097        let allocator_1 = Allocator::join(&file_for_join).unwrap();
1098
1099        let allocation_size = 2048;
1100        let size_index = size_class_index(allocation_size).unwrap();
1101        let allocations_per_slab = slab_size / allocation_size;
1102
1103        // Allocate enough to fill the first slab.
1104        let mut allocations = vec![];
1105        for _ in 0..allocations_per_slab {
1106            allocations.push(allocator_0.allocate(allocation_size).unwrap());
1107        }
1108
1109        // The first slab should be full.
1110        let slab_index = unsafe {
1111            let worker_local_list = allocator_0.worker_local_list_partial(size_index);
1112            assert!(worker_local_list.head().is_none());
1113            let worker_local_list = allocator_0.worker_local_list_full(size_index);
1114            assert!(worker_local_list.head().is_some());
1115            worker_local_list.head().unwrap()
1116        };
1117
1118        assert_eq!(remote_free_stack(&allocator_0), Vec::<usize>::new());
1119
1120        // Free the allocations to the remote free stack.
1121        let mut allocation_offsets = Vec::new();
1122        for ptr in allocations {
1123            unsafe {
1124                let offset = allocator_0.offset(ptr);
1125                allocation_offsets.push(offset);
1126                allocator_1.free_offset(offset);
1127            }
1128        }
1129        assert_eq!(
1130            remote_free_stack(&allocator_0),
1131            allocation_offsets.iter().rev().copied().collect::<Vec<_>>()
1132        );
1133        assert_eq!(
1134            allocator_0.outstanding_allocation_bytes(),
1135            allocations_per_slab as u64 * allocation_size as u64
1136        );
1137
1138        // Allocator 0 can NOT allocate in the same slab.
1139        let different_slab_allocation = allocator_0.allocate(allocation_size).unwrap();
1140        let allocation_indexes = unsafe {
1141            allocator_0.find_allocation_indexes(allocator_0.offset(different_slab_allocation))
1142        };
1143        assert_ne!(allocation_indexes.slab_index, slab_index);
1144        unsafe { allocator_0.free(different_slab_allocation) };
1145
1146        // If we clean the remote free lists, the next allocation should succeed in the same slab.
1147        allocator_0.clean_remote_frees();
1148        assert_eq!(remote_free_stack(&allocator_0), Vec::<usize>::new());
1149        assert_eq!(allocator_0.outstanding_allocation_bytes(), 0);
1150        let same_slab_allocation = allocator_0.allocate(allocation_size).unwrap();
1151        let allocation_indexes = unsafe {
1152            allocator_0.find_allocation_indexes(allocator_0.offset(same_slab_allocation))
1153        };
1154        assert_eq!(allocation_indexes.slab_index, slab_index);
1155    }
1156
1157    #[test]
1158    fn test_remote_free_batch_mixed_owners() {
1159        let slab_size = 65536; // 64 KiB
1160        let num_workers = 4;
1161        let (_file, allocator_0) = initialize_for_test(slab_size, num_workers);
1162        let allocator_1 = Allocator::join_from_existing(&allocator_0).unwrap();
1163        let allocator_2 = Allocator::join_from_existing(&allocator_0).unwrap();
1164        let allocation_size = 2048;
1165
1166        let allocations_0 = [
1167            allocator_0.allocate(allocation_size).unwrap(),
1168            allocator_0.allocate(allocation_size).unwrap(),
1169        ];
1170        let allocations_1 = [
1171            allocator_1.allocate(allocation_size).unwrap(),
1172            allocator_1.allocate(allocation_size).unwrap(),
1173        ];
1174        let allocations_2 = [
1175            allocator_2.allocate(allocation_size).unwrap(),
1176            allocator_2.allocate(allocation_size).unwrap(),
1177        ];
1178        let offsets = |allocator: &Allocator, allocations: &[NonNull<u8>; 2]| {
1179            allocations.map(|allocation| unsafe { allocator.offset(allocation) })
1180        };
1181        let offsets_0 = offsets(&allocator_0, &allocations_0);
1182        let offsets_1 = offsets(&allocator_1, &allocations_1);
1183        let offsets_2 = offsets(&allocator_2, &allocations_2);
1184
1185        let mut batch = allocator_1.remote_free_batch();
1186        unsafe {
1187            batch.free(allocations_0[0]);
1188            batch.free_offset(offsets_1[0]);
1189            batch.free_offset(offsets_2[0]);
1190        }
1191
1192        let free_only_allocator = FreeOnlyAllocator::join_from_existing(&allocator_0);
1193        let mut free_only_batch = free_only_allocator.remote_free_batch();
1194        unsafe {
1195            free_only_batch.free_offset(offsets_0[1]);
1196            free_only_batch.free(allocations_1[1]);
1197            free_only_batch.free_offset(offsets_2[1]);
1198        }
1199
1200        assert_eq!(
1201            allocator_1.outstanding_allocation_bytes(),
1202            u64::from(allocation_size)
1203        );
1204        assert_eq!(remote_free_stack(&allocator_0), Vec::<usize>::new());
1205        assert_eq!(remote_free_stack(&allocator_1), Vec::<usize>::new());
1206        assert_eq!(remote_free_stack(&allocator_2), Vec::<usize>::new());
1207
1208        batch.flush();
1209        free_only_batch.flush();
1210        assert_eq!(
1211            remote_free_stack(&allocator_0),
1212            offsets_0.into_iter().rev().collect::<Vec<_>>()
1213        );
1214        assert_eq!(remote_free_stack(&allocator_1), vec![offsets_1[1]]);
1215        assert_eq!(
1216            remote_free_stack(&allocator_2),
1217            offsets_2.into_iter().rev().collect::<Vec<_>>()
1218        );
1219
1220        allocator_0.clean_remote_frees();
1221        allocator_1.clean_remote_frees();
1222        allocator_2.clean_remote_frees();
1223        assert_eq!(allocator_0.outstanding_allocation_bytes(), 0);
1224        assert_eq!(allocator_1.outstanding_allocation_bytes(), 0);
1225        assert_eq!(allocator_2.outstanding_allocation_bytes(), 0);
1226    }
1227
1228    #[test]
1229    fn test_join_from_existing_reuses_mapping() {
1230        let slab_size = 65536; // 64 KiB
1231        let num_workers = 4;
1232        let (_file, allocator_0) = initialize_for_test(slab_size, num_workers);
1233
1234        let allocator_1 = Allocator::join_from_existing(&allocator_0).unwrap();
1235        assert_ne!(allocator_0.worker_index, allocator_1.worker_index);
1236        assert_eq!(
1237            allocator_0.base.header().as_ptr(),
1238            allocator_1.base.header().as_ptr()
1239        );
1240
1241        let free_only_allocator = FreeOnlyAllocator::join_from_existing(&allocator_0);
1242        assert_eq!(
1243            allocator_0.base.header().as_ptr(),
1244            free_only_allocator.base.header().as_ptr()
1245        );
1246    }
1247
1248    #[test]
1249    fn test_drop_original_mapping_stays_alive() {
1250        let slab_size = 65536; // 64 KiB
1251        let num_workers = 4;
1252        let (_file, allocator_0) = initialize_for_test(slab_size, num_workers);
1253
1254        // Join with a second allocator.
1255        let allocator_1 = Allocator::join_from_existing(&allocator_0).unwrap();
1256
1257        // Drop the original.
1258        drop(allocator_0);
1259
1260        // We can still allocate, read, and write through the shared mapping.
1261        let allocation_size = 2048;
1262        let allocation = allocator_1.allocate(allocation_size).unwrap();
1263        unsafe {
1264            allocation
1265                .as_ptr()
1266                .write_bytes(0xAB, allocation_size as usize);
1267            assert_eq!(allocation.as_ptr().read(), 0xAB);
1268            allocator_1.free(allocation);
1269        }
1270    }
1271
1272    #[test]
1273    fn test_worker_reuse_with_free_only() {
1274        let slab_size = 65536; // 64 KiB
1275        let num_workers = 4;
1276        let (_file, allocator_0) = initialize_for_test(slab_size, num_workers);
1277        let num_workers = allocator_0.base.layout.num_workers;
1278
1279        // Join with a free only allocator (doesn't consume a worker slot).
1280        let free_only_allocator = FreeOnlyAllocator::join_from_existing(&allocator_0);
1281
1282        // Fill all worker slots.
1283        let mut allocators = Vec::new();
1284        for _ in 0..(num_workers - 1) {
1285            allocators.push(Allocator::join_from_existing_free_only(&free_only_allocator).unwrap());
1286        }
1287        assert!(Allocator::join_from_existing_free_only(&free_only_allocator).is_err());
1288
1289        // Drop original and take its worker spot with a new allocator.
1290        drop(allocator_0);
1291        allocators.push(Allocator::join_from_existing_free_only(&free_only_allocator).unwrap());
1292        assert!(Allocator::join_from_existing_free_only(&free_only_allocator).is_err());
1293
1294        // Drop all allocators.
1295        drop(allocators);
1296
1297        // Re-fill all the allocators from our free only observer.
1298        let mut allocators = Vec::new();
1299        for _ in 0..num_workers {
1300            allocators.push(Allocator::join_from_existing_free_only(&free_only_allocator).unwrap());
1301        }
1302        assert!(Allocator::join_from_existing_free_only(&free_only_allocator).is_err());
1303
1304        // Verify we can allocate, write, and read through a re-joined allocator.
1305        let allocation_size = 2048u32;
1306        let allocation = allocators[0].allocate(allocation_size).unwrap();
1307        unsafe {
1308            allocation
1309                .as_ptr()
1310                .write_bytes(0xCD, allocation_size as usize);
1311            assert_eq!(allocation.as_ptr().read(), 0xCD);
1312            allocators[0].free(allocation);
1313        }
1314    }
1315
1316    #[test]
1317    fn test_free_only_allocator() {
1318        let slab_size = 65536; // 64 KiB
1319        let num_workers = 4;
1320        let (file, allocator) = initialize_for_test(slab_size, num_workers);
1321        let file_for_join = file.try_clone().unwrap();
1322        let free_only_allocator = FreeOnlyAllocator::join(&file_for_join).unwrap();
1323
1324        let allocation_size = 2048;
1325        let allocation = allocator.allocate(allocation_size).unwrap();
1326
1327        // SAFETY: allocation is a valid pointer allocated by the allocator.
1328        let offset = unsafe { allocator.offset(allocation) };
1329        unsafe {
1330            free_only_allocator.free_offset(offset);
1331        }
1332
1333        assert_eq!(remote_free_stack(&allocator), vec![offset]);
1334    }
1335}