use super::*;
#[test]
fn cross_thread_free_does_not_charge_non_owner_defrag_counter() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
use std::thread;
let mut owner = ThreadAllocator::<DefaultBackend>::new();
let ptr = unsafe { owner.alloc::<StandardPolicy>(32) };
assert!(
!ptr.is_null(),
"producer allocation for cross-thread free failed"
);
let ptr_usize = ptr as usize;
let handle = thread::spawn(move || {
DefaultBackend::with_allocator(|alloc| {
assert_eq!(alloc.defrag_counter, 0);
})
.expect("worker allocator slot unavailable before remote free");
unsafe {
crate::thread_free::<mnemosyne_core::StandardPolicy, DefaultBackend>(
ptr_usize as *mut u8,
);
}
DefaultBackend::with_allocator(|alloc| {
assert_eq!(
alloc.defrag_counter, 0,
"remote free charged defrag work to the non-owner allocator"
);
})
.expect("worker allocator slot unavailable after remote free");
});
handle.join().expect("cross-thread free worker panicked");
let mut reclaimed_remote_free = false;
let ptr_val = ptr as usize;
let segment_addr = ptr_val & !(mnemosyne_core::constants::SEGMENT_SIZE - 1);
let segment = segment_addr as *mut Segment;
let page_index = (ptr_val >> PAGE_SHIFT) & (PAGES_PER_SEGMENT - 1);
let max_blocks = unsafe { (*segment).pages[page_index].max_blocks() };
let mut probe_allocations = std::vec::Vec::with_capacity(max_blocks);
for _ in 0..max_blocks {
let ptr2 = unsafe { owner.alloc::<StandardPolicy>(32) };
assert!(
!ptr2.is_null(),
"reclaim probe allocation failed before reclaiming remote free"
);
probe_allocations.push(ptr2);
if ptr2 == ptr {
reclaimed_remote_free = true;
break;
}
}
assert!(
reclaimed_remote_free,
"cross-thread freed block was not reclaimed after {} small allocations",
max_blocks
);
unsafe {
for probe in probe_allocations {
crate::thread_free::<mnemosyne_core::StandardPolicy, DefaultBackend>(probe);
}
}
owner.reclaim_owned_segments();
}
#[test]
fn test_online_defragmentation_page_prioritization() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
let seg1 = unsafe { allocate_segment::<DefaultBackend>() }.expect("seg1 allocation failed");
let seg2 = unsafe { allocate_segment::<DefaultBackend>() }.expect("seg2 allocation failed");
unsafe {
Page::set_alloc_count_in_segment(seg1, 1, 1);
Page::set_alloc_count_in_segment(seg1, 2, 0);
}
unsafe {
for i in 1..mnemosyne_core::constants::PAGES_PER_SEGMENT {
Page::set_alloc_count_in_segment(seg2, i, 0);
}
}
let seg1_page2 = NonNull::new(unsafe { locate_page(seg1, 2) })
.expect("an in-range page in a live segment cannot be null");
let seg2_page1 = NonNull::new(unsafe { locate_page(seg2, 1) })
.expect("an in-range page in a live segment cannot be null");
unsafe {
alloc.push_empty_page(seg1_page2);
alloc.push_empty_page(seg2_page1);
}
let popped = unsafe { alloc.pop_best_empty_page() };
assert_eq!(popped, Some(seg1_page2));
let popped2 = unsafe { alloc.pop_best_empty_page() };
assert_eq!(popped2, Some(seg2_page1));
let popped3 = unsafe { alloc.pop_best_empty_page() };
assert_eq!(popped3, None);
unsafe {
deallocate_segment::<DefaultBackend>(seg1);
deallocate_segment::<DefaultBackend>(seg2);
}
}
#[test]
fn test_periodic_defragmentation_segment_reclaim() {
let _guard = TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
{
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
let seg1 = unsafe { allocate_segment::<DefaultBackend>() }.expect("seg1 failed");
let seg2 = unsafe { allocate_segment::<DefaultBackend>() }.expect("seg2 failed");
let seg3 = unsafe { allocate_segment::<DefaultBackend>() }.expect("seg3 failed");
unsafe {
alloc.push_owned_segment::<StandardPolicy>(seg1);
alloc.push_owned_segment::<StandardPolicy>(seg2);
alloc.push_owned_segment::<StandardPolicy>(seg3);
}
let stats = alloc.stats();
assert_eq!(stats.current_thread_owned_segments, 3);
unsafe {
alloc.periodic_defragmentation_sweep();
}
let stats = alloc.stats();
assert_eq!(stats.current_thread_owned_segments, 3);
}
{
let mut alloc = ThreadAllocator::<DefaultBackend>::new();
let seg1 = unsafe { allocate_segment::<DefaultBackend>() }.expect("seg1 failed");
let seg2 = unsafe { allocate_segment::<DefaultBackend>() }.expect("seg2 failed");
let seg3 = unsafe { allocate_segment::<DefaultBackend>() }.expect("seg3 failed");
let seg4 = unsafe { allocate_segment::<DefaultBackend>() }.expect("seg4 failed");
unsafe {
alloc.push_owned_segment::<StandardPolicy>(seg1);
alloc.push_owned_segment::<StandardPolicy>(seg2);
alloc.push_owned_segment::<StandardPolicy>(seg3);
alloc.push_owned_segment::<StandardPolicy>(seg4);
}
unsafe {
alloc.set_current_segment(Some(NonNull::new_unchecked(seg1)));
}
let stats = alloc.stats();
assert_eq!(stats.current_thread_owned_segments, 4);
unsafe {
alloc.periodic_defragmentation_sweep();
}
let stats = alloc.stats();
assert_eq!(stats.current_thread_owned_segments, 3);
assert!(alloc.is_current_segment(seg1));
}
}