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