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 let _ = crate::memory_map::unmap_file(self.header.as_ptr().cast(), self.file_size);
34 }
35}
36
37unsafe impl Send for MappedRegion {}
43unsafe 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 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 let base = unsafe { AllocatorBase::from_mapping(header, file_size) };
80 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 pub fn join(file: &File) -> Result<Self, Error> {
97 let (header, file_size) = crate::init::join(file)?;
98 let base = unsafe { AllocatorBase::from_mapping(header, file_size) };
101 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 pub fn join_from_existing(existing: &Allocator) -> Result<Self, Error> {
113 Self::join_from_base(&existing.base)
114 }
115
116 pub fn join_from_existing_free_only(existing: &FreeOnlyAllocator) -> Result<Self, Error> {
119 Self::join_from_base(&existing.base)
120 }
121
122 fn join_from_base(base: &AllocatorBase) -> Result<Self, Error> {
125 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 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 pub fn join(file: &File) -> Result<Self, Error> {
159 let (header, file_size) = crate::init::join(file)?;
160 Ok(FreeOnlyAllocator {
163 base: unsafe { AllocatorBase::from_mapping(header, file_size) },
164 })
165 }
166
167 pub fn join_from_existing(existing: &Allocator) -> Self {
169 Self::from_base(&existing.base)
170 }
171
172 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 pub fn allocate(&self, size: u32) -> Option<NonNull<u8>> {
191 if size == 0 {
193 return None;
194 }
195 let size_index = size_class_index(size)?;
196
197 let slab_index = unsafe { self.find_allocatable_slab_index(size_index) }?;
199 unsafe { self.allocate_within_slab(slab_index, size_index) }
203 }
204
205 unsafe fn find_allocatable_slab_index(&self, size_index: usize) -> Option<u32> {
212 unsafe { self.worker_local_list_partial(size_index) }
214 .head()
215 .or_else(|| self.take_slab(size_index))
216 }
217
218 unsafe fn allocate_within_slab(
225 &self,
226 slab_index: u32,
227 size_index: usize,
228 ) -> Option<NonNull<u8>> {
229 let mut free_stack = unsafe { self.slab_free_stack(slab_index) };
231 let maybe_index_within_slab = free_stack.pop();
232
233 if free_stack.is_empty() {
236 unsafe {
240 self.worker_local_list_partial(size_index)
241 .remove(slab_index);
242 }
243 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 let slab = unsafe { self.slab(slab_index) };
254 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 unsafe fn take_slab(&self, size_index: usize) -> Option<u32> {
270 let slab_index = self.global_free_list().pop()?;
271
272 unsafe { self.slab_meta(slab_index).as_ref() }.assign(self.worker_index, size_index);
274 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 let mut worker_local_list = unsafe { self.worker_local_list_partial(size_index) };
283 unsafe { worker_local_list.push(slab_index) };
285 Some(slab_index)
286 }
287}
288
289impl Allocator {
290 pub unsafe fn free(&self, ptr: NonNull<u8>) {
296 let offset = unsafe { self.offset(ptr) };
298 self.free_offset(offset);
299 }
300
301 pub unsafe fn free_offset(&self, offset: usize) {
308 let allocation_indexes = self.find_allocation_indexes(offset);
309
310 if self.worker_index
312 == unsafe { self.slab_meta(allocation_indexes.slab_index).as_ref() }
313 .assigned_worker
314 .load(Ordering::Acquire)
315 {
316 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 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 (was_full, free_stack.is_full())
337 };
338
339 match (was_full, is_empty) {
340 (true, true) => {
341 unreachable!("slab can only contain one allocation - this is not allowed");
344 }
345 (true, false) => {
346 unsafe {
350 self.worker_local_list_full(size_index)
351 .remove(allocation_indexes.slab_index);
352 }
353 unsafe {
355 self.worker_local_list_partial(size_index)
356 .push(allocation_indexes.slab_index);
357 }
358 }
359 (false, true) => {
360 unsafe {
364 self.worker_local_list_partial(size_index)
365 .remove(allocation_indexes.slab_index);
366 }
367 unsafe {
369 self.global_free_list().push(allocation_indexes.slab_index);
370 }
371 }
372 (false, false) => {
373 }
376 }
377 }
378
379 fn remote_free(&self, allocation_indexes: AllocationIndexes) {
380 unsafe {
382 self.base
383 .remote_free_list(allocation_indexes.slab_index)
384 .push(allocation_indexes.index_within_slab);
385 }
386 }
387
388 pub unsafe fn offset(&self, ptr: NonNull<u8>) -> usize {
393 self.base.offset(ptr)
394 }
395
396 pub unsafe fn ptr_from_offset(&self, offset: usize) -> NonNull<u8> {
402 self.base.ptr_from_offset(offset)
403 }
404
405 fn find_allocation_indexes(&self, offset: usize) -> AllocationIndexes {
407 self.base.find_allocation_indexes(offset)
408 }
409}
410
411impl FreeOnlyAllocator {
412 pub unsafe fn free(&self, ptr: NonNull<u8>) {
418 let offset = unsafe { self.offset(ptr) };
420 self.free_offset(offset);
421 }
422
423 pub unsafe fn free_offset(&self, offset: usize) {
430 let allocation_indexes = self.find_allocation_indexes(offset);
431 unsafe {
433 self.base
434 .remote_free_list(allocation_indexes.slab_index)
435 .push(allocation_indexes.index_within_slab);
436 }
437 }
438
439 pub unsafe fn offset(&self, ptr: NonNull<u8>) -> usize {
444 self.base.offset(ptr)
445 }
446
447 pub unsafe fn ptr_from_offset(&self, offset: usize) -> NonNull<u8> {
453 self.base.ptr_from_offset(offset)
454 }
455
456 fn find_allocation_indexes(&self, offset: usize) -> AllocationIndexes {
458 self.base.find_allocation_indexes(offset)
459 }
460}
461
462impl AllocatorBase {
463 unsafe fn from_mapping(header: NonNull<Header>, file_size: usize) -> Self {
467 let layout = {
468 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 unsafe fn offset(&self, ptr: NonNull<u8>) -> usize {
496 ptr.byte_offset_from(self.header()) as usize
497 }
498
499 unsafe fn ptr_from_offset(&self, offset: usize) -> NonNull<u8> {
505 unsafe { self.header().byte_add(offset) }.cast()
506 }
507
508 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 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 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 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 unsafe fn remote_free_list<'a>(&'a self, slab_index: u32) -> RemoteFreeList<'a> {
555 let (head, slab_item_size) = {
556 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 let slab = unsafe { self.slab(slab_index) };
563
564 unsafe { RemoteFreeList::new(slab_item_size, head, slab) }
570 }
571
572 unsafe fn slab_meta(&self, slab_index: u32) -> NonNull<SlabMeta> {
577 let offset = self.layout.slab_shared_meta_offset;
578 let slab_metas = unsafe { self.header().byte_add(offset as usize).cast::<SlabMeta>() };
580 unsafe { slab_metas.add(slab_index as usize) }
582 }
583
584 unsafe fn slab(&self, slab_index: u32) -> NonNull<u8> {
589 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 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 pub fn clean_remote_free_lists(&self) {
626 for size_index in 0..NUM_SIZE_CLASSES {
630 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 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 fn clean_remote_free_lists_for_list(&self, worker_local_list: WorkerLocalList) {
642 for slab_index in worker_local_list.iterate() {
643 let (size_index, size) = unsafe { self.slab_size_class(slab_index) };
645 let mut drained_items = 0u64;
646 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 fn free_list_elements(&self) -> &[LinkedListNode] {
670 self.base.free_list_elements()
671 }
672
673 fn global_free_list<'a>(&'a self) -> GlobalFreeList<'a> {
675 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 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 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 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 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 unsafe fn remote_free_list<'a>(&'a self, slab_index: u32) -> RemoteFreeList<'a> {
730 self.base.remote_free_list(slab_index)
731 }
732
733 unsafe fn slab_meta(&self, slab_index: u32) -> NonNull<SlabMeta> {
738 self.base.slab_meta(slab_index)
739 }
740
741 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 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 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 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; 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 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; 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 for size_index in 0..NUM_SIZE_CLASSES {
889 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 unsafe {
897 allocator.free(ptr);
898 }
899 }
900 assert_eq!(allocator.outstanding_allocation_bytes(), 0);
901
902 for size_index in 0..NUM_SIZE_CLASSES {
904 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; 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 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 check_worker_list_expectations(&allocator, size_index, true, false);
955
956 first_slab_allocations.push(allocator.allocate(allocation_size).unwrap());
958
959 check_worker_list_expectations(&allocator, size_index, false, true);
961
962 let second_slab_allocation = allocator.allocate(allocation_size).unwrap();
964
965 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 check_worker_list_expectations(&allocator, size_index, true, false);
974
975 for ptr in first_slab_allocations {
977 unsafe {
978 allocator.free(ptr);
979 }
980 }
981 check_worker_list_expectations(&allocator, size_index, true, false);
984
985 unsafe {
987 allocator.free(second_slab_allocation);
988 }
989 check_worker_list_expectations(&allocator, size_index, false, false);
991 }
992
993 #[test]
994 fn test_out_of_slabs() {
995 let slab_size = 65536; 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 assert!(unsafe { allocator.take_slab(0) }.is_none());
1005 }
1006
1007 #[test]
1008 fn test_remote_free_lists() {
1009 let slab_size = 65536; 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 let mut allocations = vec![];
1021 for _ in 0..allocations_per_slab {
1022 allocations.push(allocator_0.allocate(allocation_size).unwrap());
1023 }
1024
1025 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 let remote_free_list = unsafe { allocator_0.remote_free_list(slab_index) };
1036 assert!(remote_free_list.iterate().next().is_none());
1037
1038 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 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 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; 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; let num_workers = 4;
1092 let (_file, allocator_0) = initialize_for_test(slab_size, num_workers);
1093
1094 let allocator_1 = Allocator::join_from_existing(&allocator_0).unwrap();
1096
1097 drop(allocator_0);
1099
1100 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; 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 let free_only_allocator = FreeOnlyAllocator::join_from_existing(&allocator_0);
1121
1122 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(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(allocators);
1136
1137 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 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; 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 unsafe {
1172 let offset = allocator.offset(allocation);
1173 free_only_allocator.free_offset(offset);
1174 }
1175
1176 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}