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 _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 let _ = crate::memory_map::unmap_file(self.header.as_ptr().cast(), self.file_size);
40 }
41}
42
43unsafe impl Send for MappedRegion {}
49unsafe 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 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 let base = unsafe { AllocatorBase::from_mapping(header, file_size) };
87 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 pub fn join(file: &File) -> Result<Self, Error> {
104 let (header, file_size) = crate::init::join(file)?;
105 let base = unsafe { AllocatorBase::from_mapping(header, file_size) };
108 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 pub fn join_from_existing(existing: &Allocator) -> Result<Self, Error> {
120 Self::join_from_base(&existing.base)
121 }
122
123 pub fn join_from_existing_free_only(existing: &FreeOnlyAllocator) -> Result<Self, Error> {
126 Self::join_from_base(&existing.base)
127 }
128
129 fn join_from_base(base: &AllocatorBase) -> Result<Self, Error> {
132 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 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 pub fn join(file: &File) -> Result<Self, Error> {
178 let (header, file_size) = crate::init::join(file)?;
179 Ok(FreeOnlyAllocator {
182 base: unsafe { AllocatorBase::from_mapping(header, file_size) },
183 })
184 }
185
186 pub fn join_from_existing(existing: &Allocator) -> Self {
188 Self::from_base(&existing.base)
189 }
190
191 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 pub fn allocate(&self, size: u32) -> Option<NonNull<u8>> {
214 if size == 0 {
216 return None;
217 }
218 let size_index = size_class_index(size)?;
219
220 let slab_index = unsafe { self.find_allocatable_slab_index(size_index) }?;
222 unsafe { self.allocate_within_slab(slab_index, size_index) }
226 }
227
228 unsafe fn find_allocatable_slab_index(&self, size_index: usize) -> Option<u32> {
235 unsafe { self.worker_local_list_partial(size_index) }
237 .head()
238 .or_else(|| self.take_slab(size_index))
239 }
240
241 unsafe fn allocate_within_slab(
248 &self,
249 slab_index: u32,
250 size_index: usize,
251 ) -> Option<NonNull<u8>> {
252 let mut free_stack = unsafe { self.slab_free_stack(slab_index) };
254 let maybe_index_within_slab = free_stack.pop();
255
256 if free_stack.is_empty() {
259 unsafe {
263 self.worker_local_list_partial(size_index)
264 .remove(slab_index);
265 }
266 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 let slab = unsafe { self.slab(slab_index) };
277 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 unsafe fn take_slab(&self, size_index: usize) -> Option<u32> {
293 let slab_index = self.global_free_list().pop()?;
294
295 unsafe { self.slab_meta(slab_index).as_ref() }.assign(self.worker_index, size_index);
297 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 let mut worker_local_list = unsafe { self.worker_local_list_partial(size_index) };
306 unsafe { worker_local_list.push(slab_index) };
308 Some(slab_index)
309 }
310}
311
312impl Allocator {
313 pub unsafe fn free(&self, ptr: NonNull<u8>) {
319 let offset = unsafe { self.offset(ptr) };
321 self.free_offset(offset);
322 }
323
324 pub unsafe fn free_offset(&self, offset: usize) {
331 let allocation_indexes = self.find_allocation_indexes(offset);
332
333 if self.worker_index
335 == unsafe { self.slab_meta(allocation_indexes.slab_index).as_ref() }
336 .assigned_worker
337 .load(Ordering::Acquire)
338 {
339 unsafe { self.free_local(allocation_indexes) };
342 } else {
343 self.remote_free(offset, allocation_indexes.slab_index);
344 }
345 }
346
347 pub(crate) unsafe fn free_local(&self, allocation_indexes: AllocationIndexes) {
353 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 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 (was_full, free_stack.is_full())
371 };
372
373 match (was_full, is_empty) {
374 (true, true) => {
375 unreachable!("slab can only contain one allocation - this is not allowed");
378 }
379 (true, false) => {
380 unsafe {
384 self.worker_local_list_full(size_index)
385 .remove(allocation_indexes.slab_index);
386 }
387 unsafe {
389 self.worker_local_list_partial(size_index)
390 .push(allocation_indexes.slab_index);
391 }
392 }
393 (false, true) => {
394 unsafe {
398 self.worker_local_list_partial(size_index)
399 .remove(allocation_indexes.slab_index);
400 }
401 unsafe {
403 self.slab_meta(allocation_indexes.slab_index)
404 .as_ref()
405 .assigned_worker
406 .store(NULL_U32, Ordering::Release);
407 }
408 unsafe {
410 self.global_free_list().push(allocation_indexes.slab_index);
411 }
412 }
413 (false, false) => {
414 }
417 }
418 }
419
420 fn remote_free(&self, offset: usize, slab_index: u32) {
421 self.base.remote_free(offset, slab_index);
422 }
423
424 pub unsafe fn offset(&self, ptr: NonNull<u8>) -> usize {
429 self.base.offset(ptr)
430 }
431
432 pub unsafe fn ptr_from_offset(&self, offset: usize) -> NonNull<u8> {
438 self.base.ptr_from_offset(offset)
439 }
440
441 fn find_allocation_indexes(&self, offset: usize) -> AllocationIndexes {
443 self.base.find_allocation_indexes(offset)
444 }
445}
446
447impl FreeOnlyAllocator {
448 pub unsafe fn free(&self, ptr: NonNull<u8>) {
454 let offset = unsafe { self.offset(ptr) };
456 self.free_offset(offset);
457 }
458
459 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 pub unsafe fn offset(&self, ptr: NonNull<u8>) -> usize {
475 self.base.offset(ptr)
476 }
477
478 pub unsafe fn ptr_from_offset(&self, offset: usize) -> NonNull<u8> {
484 self.base.ptr_from_offset(offset)
485 }
486
487 fn find_allocation_indexes(&self, offset: usize) -> AllocationIndexes {
489 self.base.find_allocation_indexes(offset)
490 }
491}
492
493impl AllocatorBase {
494 unsafe fn from_mapping(header: NonNull<Header>, file_size: usize) -> Self {
498 let layout = {
499 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 unsafe fn offset(&self, ptr: NonNull<u8>) -> usize {
528 ptr.byte_offset_from(self.header()) as usize
529 }
530
531 unsafe fn ptr_from_offset(&self, offset: usize) -> NonNull<u8> {
537 unsafe { self.header().byte_add(offset) }.cast()
538 }
539
540 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 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 fn remote_free(&self, offset: usize, slab_index: u32) {
574 debug_assert_ne!(offset, NULL_USIZE);
575
576 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 unsafe { self.publish_remote_free_chain(worker_index, offset, offset) };
587 }
588
589 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 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 pub(crate) unsafe fn set_remote_free_next(&self, offset: usize, next: usize) {
614 debug_assert_ne!(offset, NULL_USIZE);
615 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 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 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 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 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 unsafe fn slab_meta(&self, slab_index: u32) -> NonNull<SlabMeta> {
673 let offset = self.layout.slab_shared_meta_offset;
674 let slab_metas = unsafe { self.header().byte_add(offset as usize).cast::<SlabMeta>() };
676 unsafe { slab_metas.add(slab_index as usize) }
678 }
679
680 unsafe fn slab(&self, slab_index: u32) -> NonNull<u8> {
685 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 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 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 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 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 fn free_list_elements(&self) -> &[LinkedListNode] {
747 self.base.free_list_elements()
748 }
749
750 fn global_free_list<'a>(&'a self) -> GlobalFreeList<'a> {
752 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 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 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 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 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 unsafe fn slab_meta(&self, slab_index: u32) -> NonNull<SlabMeta> {
807 self.base.slab_meta(slab_index)
808 }
809
810 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 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 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 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; 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 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; 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 for size_index in 0..NUM_SIZE_CLASSES {
973 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 unsafe {
981 allocator.free(ptr);
982 }
983 }
984 assert_eq!(allocator.outstanding_allocation_bytes(), 0);
985
986 for size_index in 0..NUM_SIZE_CLASSES {
988 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; 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 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 check_worker_list_expectations(&allocator, size_index, true, false);
1039
1040 first_slab_allocations.push(allocator.allocate(allocation_size).unwrap());
1042
1043 check_worker_list_expectations(&allocator, size_index, false, true);
1045
1046 let second_slab_allocation = allocator.allocate(allocation_size).unwrap();
1048
1049 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 check_worker_list_expectations(&allocator, size_index, true, false);
1058
1059 for ptr in first_slab_allocations {
1061 unsafe {
1062 allocator.free(ptr);
1063 }
1064 }
1065 check_worker_list_expectations(&allocator, size_index, true, false);
1068
1069 unsafe {
1071 allocator.free(second_slab_allocation);
1072 }
1073 check_worker_list_expectations(&allocator, size_index, false, false);
1075 }
1076
1077 #[test]
1078 fn test_out_of_slabs() {
1079 let slab_size = 65536; 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 assert!(unsafe { allocator.take_slab(0) }.is_none());
1089 }
1090
1091 #[test]
1092 fn test_remote_free_lists() {
1093 let slab_size = 65536; 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 let mut allocations = vec![];
1105 for _ in 0..allocations_per_slab {
1106 allocations.push(allocator_0.allocate(allocation_size).unwrap());
1107 }
1108
1109 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 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 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 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; 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; 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; let num_workers = 4;
1252 let (_file, allocator_0) = initialize_for_test(slab_size, num_workers);
1253
1254 let allocator_1 = Allocator::join_from_existing(&allocator_0).unwrap();
1256
1257 drop(allocator_0);
1259
1260 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; 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 let free_only_allocator = FreeOnlyAllocator::join_from_existing(&allocator_0);
1281
1282 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(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(allocators);
1296
1297 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 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; 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 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}