use alloc::alloc::{Layout, alloc, dealloc, handle_alloc_error};
use alloc::vec::Vec;
use core::marker::PhantomData;
use core::mem::{align_of, size_of};
use core::ptr::{self, NonNull};
use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
const FIRST_CHUNK_SIZE: usize = 8 * 1024;
const MAX_PARKED_CHUNK_SIZE: usize = 1024 * 1024;
const CHUNK_ALIGN: usize = 16;
type Footer = AtomicPtr<u8>;
const FOOTER_SIZE: usize = size_of::<Footer>();
const FOOTER_ALIGN: usize = align_of::<Footer>();
const DEAD: usize = 1;
const ORPHAN: usize = 2;
const TAG_MASK: usize = ORPHAN | DEAD;
const _: () = assert!(FOOTER_ALIGN > TAG_MASK && align_of::<Chunk>() > TAG_MASK);
struct Chunk {
prev: *mut Chunk,
next: *mut Chunk,
layout: Layout,
bufs: Buffers,
live: AtomicUsize,
}
type Buffers = [RawBuf; 3];
const NO_BUFFERS: Buffers = [RawBuf::EMPTY; 3];
#[derive(Copy, Clone)]
pub(crate) enum Buffer {
SinkStack = 0,
SerializeStack = 1,
Scratch = 2,
}
const MAX_BUFFER_SIZE: [usize; 3] = [64 * 1024, 64 * 1024, 256 * 1024];
#[derive(Copy, Clone)]
struct RawBuf {
ptr: *mut u8,
cap: usize,
size: usize,
align: usize,
}
impl RawBuf {
const EMPTY: RawBuf = RawBuf {
ptr: ptr::null_mut(),
cap: 0,
size: 0,
align: 1,
};
unsafe fn free(self) {
if self.cap != 0 {
unsafe {
dealloc(
self.ptr,
Layout::from_size_align_unchecked(self.size * self.cap, self.align),
)
}
}
}
}
unsafe fn free_buffers(bufs: &mut Buffers) {
for buf in bufs.iter_mut() {
unsafe { core::mem::replace(buf, RawBuf::EMPTY).free() };
}
}
const CHUNK_HEADER: usize = size_of::<Chunk>().next_multiple_of(CHUNK_ALIGN);
impl Chunk {
fn alloc(size: usize, data: usize) -> NonNull<Chunk> {
let size = size.max(CHUNK_HEADER + data);
let Ok(layout) = Layout::from_size_align(size, CHUNK_ALIGN) else {
panic!("sink too large");
};
let Some(chunk) = NonNull::new(unsafe { alloc(layout) }) else {
handle_alloc_error(layout)
};
let chunk = chunk.cast::<Chunk>();
unsafe {
chunk.as_ptr().write(Chunk {
prev: ptr::null_mut(),
next: ptr::null_mut(),
layout,
bufs: NO_BUFFERS,
live: AtomicUsize::new(0),
})
};
chunk
}
unsafe fn free(chunk: *mut Chunk) {
unsafe {
free_buffers(&mut (*chunk).bufs);
dealloc(chunk.cast(), (*chunk).layout)
}
}
#[inline(always)]
fn start(chunk: *mut Chunk) -> *mut u8 {
chunk.cast::<u8>().wrapping_add(CHUNK_HEADER)
}
#[inline(always)]
fn end(chunk: *mut Chunk) -> *mut u8 {
chunk
.cast::<u8>()
.wrapping_add(unsafe { (*chunk).layout.size() })
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub(crate) enum Alloc {
Arena,
Heap,
}
pub(crate) struct Arena {
top: *mut u8,
end: *mut u8,
base: *mut u8,
chunk: *mut Chunk,
}
unsafe impl Send for Arena {}
unsafe impl Sync for Arena {}
impl Default for Arena {
fn default() -> Arena {
Arena::new()
}
}
impl Arena {
pub(crate) const fn new() -> Arena {
Arena {
top: ptr::null_mut(),
end: ptr::null_mut(),
base: ptr::null_mut(),
chunk: ptr::null_mut(),
}
}
#[inline(always)]
fn bufs(&mut self) -> Option<&mut Buffers> {
if self.chunk.is_null() {
return None;
}
unsafe { Some(&mut (*self.base.wrapping_sub(CHUNK_HEADER).cast::<Chunk>()).bufs) }
}
#[inline]
pub(crate) fn take_vec<T>(&mut self, kind: Buffer) -> Option<Vec<T>> {
if self.chunk.is_null() {
self.take_parked();
}
let buf = core::mem::replace(&mut self.bufs()?[kind as usize], RawBuf::EMPTY);
if buf.cap == 0 {
return None;
}
if buf.size == size_of::<T>() && buf.align == align_of::<T>() {
Some(unsafe { Vec::from_raw_parts(buf.ptr.cast::<T>(), 0, buf.cap) })
} else {
unsafe { buf.free() };
None
}
}
#[inline]
pub(crate) fn put_vec<T>(&mut self, kind: Buffer, vec: Vec<T>) {
let size = size_of::<T>();
if vec.capacity() == 0
|| size == 0
|| vec.capacity() * size > MAX_BUFFER_SIZE[kind as usize]
{
return;
}
let Some(bufs) = self.bufs() else {
return;
};
let mut vec = core::mem::ManuallyDrop::new(vec);
vec.clear();
let buf = RawBuf {
ptr: vec.as_mut_ptr().cast(),
cap: vec.capacity(),
size,
align: align_of::<T>(),
};
let old = core::mem::replace(&mut bufs[kind as usize], buf);
unsafe { old.free() };
}
#[cold]
fn take_parked(&mut self) {
if let Some(chunk) = parked::take(0) {
self.set_first_chunk(chunk);
}
}
fn set_first_chunk(&mut self, chunk: NonNull<Chunk>) {
debug_assert!(self.chunk.is_null());
self.chunk = chunk.as_ptr();
self.base = Chunk::start(self.chunk);
self.top = self.base;
self.end = Chunk::end(self.chunk);
}
#[inline]
pub(crate) fn alloc(&mut self, layout: Layout) -> NonNull<u8> {
self.reclaim();
let top = self.top;
match place(top, self.end, layout) {
Some((block, footer)) => unsafe { self.commit(block, footer, top) },
None => self.alloc_slow(layout),
}
}
#[inline(always)]
unsafe fn commit(&mut self, block: *mut u8, footer: *mut u8, prev_top: *mut u8) -> NonNull<u8> {
unsafe {
footer.cast::<Footer>().write(AtomicPtr::new(prev_top));
self.top = footer.add(FOOTER_SIZE);
NonNull::new_unchecked(block)
}
}
#[cold]
#[inline(never)]
fn alloc_slow(&mut self, layout: Layout) -> NonNull<u8> {
let needed = layout.size() + layout.align() + FOOTER_ALIGN + FOOTER_SIZE;
let prev_top = self.top;
if self.chunk.is_null() {
let chunk =
parked::take(needed).unwrap_or_else(|| Chunk::alloc(FIRST_CHUNK_SIZE, needed));
self.set_first_chunk(chunk);
let top = self.top;
let (block, footer) = place(top, self.end, layout).expect("chunk too small");
return unsafe { self.commit(block, footer, top) };
}
unsafe {
let mut next = (*self.chunk).next;
if !next.is_null() && place(Chunk::start(next), Chunk::end(next), layout).is_none() {
free_chunks(next);
(*self.chunk).next = ptr::null_mut();
next = ptr::null_mut();
}
if next.is_null() {
let size = (*self.chunk).layout.size().saturating_mul(2);
next = Chunk::alloc(size, needed).as_ptr();
(*next).prev = self.chunk;
(*self.chunk).next = next;
}
self.chunk = next;
self.end = Chunk::end(next);
let start = Chunk::start(next);
let (block, footer) = place(start, self.end, layout).expect("chunk too small");
self.commit(block, footer, prev_top)
}
}
#[inline(always)]
fn reclaim(&mut self) {
if self.top != self.base && is_dead(self.top) {
self.reclaim_slow();
}
}
#[inline(always)]
pub(crate) unsafe fn pop(&mut self, block: *mut u8, size: usize) -> bool {
unsafe {
let footer = footer_of(block, size);
if footer.wrapping_add(FOOTER_SIZE) != self.top {
return false;
}
let prev = (*footer.cast::<Footer>()).load(Ordering::Relaxed);
if prev >= Chunk::start(self.chunk) && prev <= self.end {
self.top = prev;
return true;
}
}
false
}
#[inline(never)]
fn reclaim_slow(&mut self) {
while self.top != self.base {
let prev = unsafe { footer_of_top(self.top).load(Ordering::Acquire) };
if prev.addr() & DEAD == 0 {
break;
}
self.top = prev.map_addr(|addr| addr & !DEAD);
unsafe {
while self.top < Chunk::start(self.chunk) || self.top > Chunk::end(self.chunk) {
self.chunk = (*self.chunk).prev;
self.end = Chunk::end(self.chunk);
}
}
}
}
#[cfg(test)]
pub(crate) fn is_empty(&mut self) -> bool {
self.reclaim();
self.top == self.base
}
}
impl Drop for Arena {
fn drop(&mut self) {
if self.chunk.is_null() {
return;
}
self.reclaim();
if self.top != self.base {
unsafe {
let next = (*self.chunk).next;
if !next.is_null() {
(*self.chunk).next = ptr::null_mut();
free_chunks(next);
}
if let Some(bufs) = self.bufs() {
free_buffers(bufs);
}
self.orphan();
}
return;
}
unsafe {
let first = self.base.wrapping_sub(CHUNK_HEADER).cast::<Chunk>();
let mut parked = ptr::null_mut::<Chunk>();
let mut chunk = first;
while !chunk.is_null() {
let size = (*chunk).layout.size();
if size <= MAX_PARKED_CHUNK_SIZE
&& (parked.is_null() || size > (*parked).layout.size())
{
parked = chunk;
}
chunk = (*chunk).next;
}
if !parked.is_null() && parked != first {
free_buffers(&mut (*parked).bufs);
(*parked).bufs = core::mem::replace(&mut (*first).bufs, NO_BUFFERS);
}
let mut chunk = first;
while !chunk.is_null() {
let next = (*chunk).next;
if chunk != parked {
Chunk::free(chunk);
}
chunk = next;
}
if let Some(parked) = NonNull::new(parked) {
(*parked.as_ptr()).prev = ptr::null_mut();
(*parked.as_ptr()).next = ptr::null_mut();
#[cfg(test)]
PARKED_SIZE.with(|size| size.set((*parked.as_ptr()).layout.size()));
if let Some(chunk) = parked::park(parked) {
Chunk::free(chunk.as_ptr());
}
}
}
}
}
impl Arena {
#[cold]
#[inline(never)]
#[cfg(target_has_atomic = "ptr")]
unsafe fn orphan(&mut self) {
#[cfg(test)]
ORPHANED.with(|orphaned| orphaned.set(orphaned.get() + 1));
unsafe {
let mut chunk = self.chunk;
while !chunk.is_null() {
(*chunk).live.store(1, Ordering::Relaxed);
chunk = (*chunk).prev;
}
let mut top = self.top;
let mut chunk = self.chunk;
while top != self.base {
let footer = footer_of_top(top);
let live = &(*chunk).live;
let orphan = chunk.cast::<u8>().map_addr(|addr| addr | ORPHAN);
let mut prev = footer.load(Ordering::Acquire);
while prev.addr() & DEAD == 0 {
live.fetch_add(1, Ordering::Relaxed);
match footer.compare_exchange(prev, orphan, Ordering::AcqRel, Ordering::Acquire)
{
Ok(_) => break,
Err(current) => {
live.fetch_sub(1, Ordering::Relaxed);
prev = current;
}
}
}
top = prev.map_addr(|addr| addr & !DEAD);
while top < Chunk::start(chunk) || top > Chunk::end(chunk) {
chunk = (*chunk).prev;
}
}
let mut chunk = self.chunk;
while !chunk.is_null() {
let prev = (*chunk).prev;
release_orphaned_chunk(chunk);
chunk = prev;
}
}
self.chunk = ptr::null_mut();
self.top = ptr::null_mut();
self.base = ptr::null_mut();
self.end = ptr::null_mut();
}
#[cfg(not(target_has_atomic = "ptr"))]
unsafe fn orphan(&mut self) {
#[cfg(test)]
ORPHANED.with(|orphaned| orphaned.set(orphaned.get() + 1));
}
}
#[cfg(target_has_atomic = "ptr")]
unsafe fn release_orphaned_chunk(chunk: *mut Chunk) {
unsafe {
if (*chunk).live.fetch_sub(1, Ordering::Release) == 1 {
core::sync::atomic::fence(Ordering::Acquire);
Chunk::free(chunk);
}
}
}
#[cfg(test)]
std::thread_local! {
pub(crate) static ORPHANED: core::cell::Cell<usize> = const { core::cell::Cell::new(0) };
pub(crate) static PARKED_SIZE: core::cell::Cell<usize> = const { core::cell::Cell::new(0) };
}
unsafe fn free_chunks(mut chunk: *mut Chunk) {
while !chunk.is_null() {
unsafe {
let next = (*chunk).next;
Chunk::free(chunk);
chunk = next;
}
}
}
#[inline(always)]
fn place(top: *mut u8, end: *mut u8, layout: Layout) -> Option<(*mut u8, *mut u8)> {
let block_pad = top.addr().wrapping_neg() & (layout.align() - 1);
let footer_offset = (block_pad + layout.size()).next_multiple_of(FOOTER_ALIGN);
let new_top_offset = footer_offset + FOOTER_SIZE;
if top.is_null() || new_top_offset > end.addr().wrapping_sub(top.addr()) {
return None;
}
Some((top.wrapping_add(block_pad), top.wrapping_add(footer_offset)))
}
#[inline(always)]
unsafe fn footer_of_top<'x>(top: *mut u8) -> &'x Footer {
unsafe { &*top.sub(FOOTER_SIZE).cast::<Footer>() }
}
#[inline(always)]
unsafe fn footer_of(block: *mut u8, size: usize) -> *mut u8 {
unsafe {
block
.add(size)
.map_addr(|addr| addr.next_multiple_of(FOOTER_ALIGN))
}
}
#[inline(always)]
fn is_dead(top: *mut u8) -> bool {
unsafe { footer_of_top(top).load(Ordering::Acquire).addr() & DEAD != 0 }
}
#[inline]
pub(crate) unsafe fn release(block: *mut u8, size: usize) {
let footer = unsafe { &*footer_of(block, size).cast::<Footer>() };
#[cfg(target_has_atomic = "ptr")]
{
let mut prev = footer.load(Ordering::Relaxed);
while let Err(current) = footer.compare_exchange_weak(
prev,
prev.map_addr(|addr| addr | DEAD),
Ordering::Release,
Ordering::Relaxed,
) {
prev = current;
}
if prev.addr() & ORPHAN != 0 {
unsafe { release_orphaned_block(prev) };
}
}
#[cfg(not(target_has_atomic = "ptr"))]
{
let prev = footer.load(Ordering::Relaxed);
footer.store(prev.map_addr(|addr| addr | DEAD), Ordering::Release);
}
}
#[cold]
#[inline(never)]
#[cfg(target_has_atomic = "ptr")]
unsafe fn release_orphaned_block(footer: *mut u8) {
core::sync::atomic::fence(Ordering::Acquire);
let chunk = footer.map_addr(|addr| addr & !ORPHAN).cast::<Chunk>();
unsafe { release_orphaned_chunk(chunk) };
}
pub(crate) struct ArenaBox<T: ?Sized> {
ptr: NonNull<T>,
_marker: PhantomData<T>,
}
unsafe impl<T: ?Sized + Send> Send for ArenaBox<T> {}
unsafe impl<T: ?Sized + Sync> Sync for ArenaBox<T> {}
impl<T> ArenaBox<T> {
#[inline(always)]
pub(crate) fn new(value: T, arena: &mut Arena) -> ArenaBox<T> {
let layout = Layout::new::<T>();
let ptr = if layout.size() == 0 {
NonNull::dangling()
} else {
arena.alloc(layout).cast::<T>()
};
unsafe { ptr.as_ptr().write(value) };
ArenaBox {
ptr,
_marker: PhantomData,
}
}
}
impl<T: ?Sized> ArenaBox<T> {
#[inline(always)]
pub(crate) unsafe fn from_raw(ptr: *mut T) -> ArenaBox<T> {
ArenaBox {
ptr: unsafe { NonNull::new_unchecked(ptr) },
_marker: PhantomData,
}
}
#[inline(always)]
pub(crate) fn into_raw(this: ArenaBox<T>) -> NonNull<T> {
let ptr = this.ptr;
core::mem::forget(this);
ptr
}
#[cfg(test)]
pub fn as_ptr(&self) -> NonNull<T> {
self.ptr
}
#[inline(always)]
pub(crate) fn ptr(&self) -> NonNull<T> {
self.ptr
}
#[inline(always)]
pub(crate) fn get(&self) -> &T {
unsafe { self.ptr.as_ref() }
}
#[inline(always)]
pub(crate) fn get_mut(&mut self) -> &mut T {
unsafe { self.ptr.as_mut() }
}
}
impl<T: ?Sized> ArenaBox<T> {
#[inline(always)]
pub(crate) fn release_in(this: ArenaBox<T>, arena: &mut Arena) {
let ptr = ArenaBox::into_raw(this);
unsafe {
let size = size_of_val(ptr.as_ref());
let release = Release(ptr.cast(), size);
ptr::drop_in_place(ptr.as_ptr());
if size != 0 && arena.pop(ptr.as_ptr().cast(), size) {
core::mem::forget(release);
}
}
}
}
impl<T: ?Sized> Drop for ArenaBox<T> {
fn drop(&mut self) {
unsafe {
let size = size_of_val(self.ptr.as_ref());
let _release = Release(self.ptr.cast(), size);
ptr::drop_in_place(self.ptr.as_ptr());
}
}
}
pub(crate) struct Release(pub(crate) NonNull<u8>, pub(crate) usize);
impl Drop for Release {
#[inline(always)]
fn drop(&mut self) {
if self.1 != 0 {
unsafe { release(self.0.as_ptr(), self.1) };
}
}
}
#[cfg(target_has_atomic = "ptr")]
mod parked {
use core::ptr::{self, NonNull};
use core::sync::atomic::{AtomicPtr, Ordering};
use super::Chunk;
const SLOTS: usize = 8;
#[repr(align(128))]
struct Slot(AtomicPtr<Chunk>);
static PARKED: [Slot; SLOTS] = [const { Slot(AtomicPtr::new(ptr::null_mut())) }; SLOTS];
#[inline(always)]
fn first_slot() -> usize {
let local = 0u8;
(ptr::addr_of!(local).addr() >> 16) % SLOTS
}
pub(super) fn take(needed: usize) -> Option<NonNull<Chunk>> {
let first = first_slot();
for idx in 0..SLOTS {
let slot = &PARKED[(first + idx) % SLOTS].0;
if slot.load(Ordering::Relaxed).is_null() {
continue;
}
let chunk = NonNull::new(slot.swap(ptr::null_mut(), Ordering::Acquire))?;
let start = Chunk::start(chunk.as_ptr());
let end = Chunk::end(chunk.as_ptr());
if needed <= end.addr() - start.addr() {
return Some(chunk);
}
unsafe { Chunk::free(chunk.as_ptr()) };
return None;
}
None
}
pub(super) fn park(chunk: NonNull<Chunk>) -> Option<NonNull<Chunk>> {
let first = first_slot();
for idx in 0..SLOTS {
let slot = &PARKED[(first + idx) % SLOTS].0;
if slot
.compare_exchange(
ptr::null_mut(),
chunk.as_ptr(),
Ordering::Release,
Ordering::Relaxed,
)
.is_ok()
{
return None;
}
}
Some(chunk)
}
}
#[cfg(not(target_has_atomic = "ptr"))]
mod parked {
use core::ptr::NonNull;
use super::Chunk;
pub(super) fn take(_needed: usize) -> Option<NonNull<Chunk>> {
None
}
pub(super) fn park(chunk: NonNull<Chunk>) -> Option<NonNull<Chunk>> {
Some(chunk)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::sync::Arc;
use alloc::vec::Vec;
#[test]
fn test_lifo() {
let mut arena = Arena::new();
let a = ArenaBox::new([1u64; 4], &mut arena);
let b = ArenaBox::new(2u8, &mut arena);
let b_addr = b.as_ptr().addr();
drop(b);
let c = ArenaBox::new(3u8, &mut arena);
assert_eq!(c.as_ptr().addr(), b_addr);
assert_eq!(*a.get(), [1; 4]);
drop(c);
drop(a);
assert!(arena.is_empty());
}
#[test]
fn test_out_of_order() {
let mut arena = Arena::new();
let a = ArenaBox::new(1u32, &mut arena);
let b = ArenaBox::new(2u32, &mut arena);
let c = ArenaBox::new(3u32, &mut arena);
drop(a);
drop(c);
assert!(!arena.is_empty());
assert_eq!(*b.get(), 2);
drop(b);
assert!(arena.is_empty());
}
#[test]
fn test_chunks() {
let rc = Arc::new(());
let mut arena = Arena::new();
let mut big_boxes = Vec::new();
for round in 0..3 {
let mut boxes = Vec::new();
for idx in 0..if cfg!(miri) { 300 } else { 3000 } {
boxes.push(ArenaBox::new((rc.clone(), [idx as u8; 24]), &mut arena));
if idx % 1000 == 0 {
let big = ArenaBox::new([round as u8; 20000], &mut arena);
assert_eq!(big.get()[19999], round as u8);
big_boxes.push(big);
}
}
for (idx, b) in boxes.iter().enumerate() {
assert_eq!(b.get().1[0], idx as u8);
}
let mut index = 0;
while !boxes.is_empty() {
index = (index + 7) % boxes.len();
boxes.swap_remove(index);
}
assert_eq!(Arc::strong_count(&rc), 1);
}
assert!(!arena.is_empty());
big_boxes.clear();
assert!(arena.is_empty());
}
#[test]
fn test_buffers() {
for chunks in [1, 3] {
let mut arena = Arena::new();
let boxes = (0..chunks * 300)
.map(|idx| ArenaBox::new([idx as u64; 4], &mut arena))
.collect::<Vec<_>>();
let mut vec = arena.take_vec::<u64>(Buffer::SinkStack).unwrap_or_default();
vec.extend(0..100u64);
let cap = vec.capacity();
arena.put_vec(Buffer::SinkStack, vec);
assert!(arena.take_vec::<u8>(Buffer::SerializeStack).is_none());
let vec = arena.take_vec::<u64>(Buffer::SinkStack).unwrap();
assert!(vec.is_empty() && vec.capacity() == cap);
arena.put_vec(Buffer::SinkStack, vec);
drop(boxes);
drop(arena);
let mut arena = Arena::new();
if let Some(vec) = arena.take_vec::<u64>(Buffer::SinkStack) {
assert!(vec.is_empty() && vec.capacity() == cap);
}
}
}
#[test]
fn test_parks_largest_chunk() {
let mut arena = Arena::new();
let boxes = (0..2000)
.map(|idx| ArenaBox::new([idx as u64; 4], &mut arena))
.collect::<Vec<_>>();
let largest = unsafe { (*arena.chunk).layout.size() };
assert!(largest > FIRST_CHUNK_SIZE);
drop(boxes);
drop(arena);
assert_eq!(PARKED_SIZE.with(|size| size.get()), largest);
let mut arena = Arena::new();
let small = ArenaBox::new(1u8, &mut arena);
let first = unsafe { (*arena.chunk).layout.size() };
let big = arena.alloc(Layout::from_size_align(2 * MAX_PARKED_CHUNK_SIZE, 8).unwrap());
unsafe { release(big.as_ptr(), 2 * MAX_PARKED_CHUNK_SIZE) };
drop(small);
drop(arena);
assert_eq!(PARKED_SIZE.with(|size| size.get()), first);
}
#[test]
fn test_alignment() {
#[repr(align(64))]
struct Aligned(u8);
let mut arena = Arena::new();
let a = ArenaBox::new(1u8, &mut arena);
let b = ArenaBox::new(Aligned(2), &mut arena);
assert_eq!(b.as_ptr().addr().get() % 64, 0);
assert_eq!(b.get().0, 2);
let c = ArenaBox::new((), &mut arena);
drop((a, b, c));
assert!(arena.is_empty());
}
#[test]
fn test_large_blocks() {
for align in [1, 2, 8, 16, 64] {
for size in [8191, 8193, 16_383, 20_003, 40_001] {
let layout = Layout::from_size_align(size, align).unwrap();
for after_small in [false, true] {
let mut arena = Arena::new();
let small = after_small.then(|| ArenaBox::new(1u8, &mut arena));
let block = arena.alloc(layout);
assert_eq!(block.as_ptr().addr() % align, 0);
unsafe {
block.as_ptr().write_bytes(0xaa, size);
release(block.as_ptr(), size);
}
drop(small);
assert!(arena.is_empty());
}
}
}
}
#[test]
fn test_other_threads() {
let mut arena = Arena::new();
let boxes = (0..10)
.map(|idx| ArenaBox::new(idx, &mut arena))
.collect::<Vec<_>>();
std::thread::spawn(move || drop(boxes)).join().unwrap();
assert!(arena.is_empty());
let threads = (0..4)
.map(|_| {
std::thread::spawn(|| {
for _ in 0..if cfg!(miri) { 3 } else { 100 } {
let mut arena = Arena::new();
let a = ArenaBox::new([0u8; 100], &mut arena);
let b = ArenaBox::new(1u64, &mut arena);
drop((a, b));
}
})
})
.collect::<Vec<_>>();
for thread in threads {
thread.join().unwrap();
}
}
fn orphaned() -> usize {
ORPHANED.with(|orphaned| orphaned.get())
}
#[test]
fn test_leaked_arena() {
let orphaned_before = orphaned();
let mut arena = Arena::new();
let dead = ArenaBox::new(1u64, &mut arena);
let a = ArenaBox::new(alloc::string::String::from("alive"), &mut arena);
let b = ArenaBox::new(2u64, &mut arena);
drop(dead);
drop(b);
drop(arena);
assert_eq!(orphaned(), orphaned_before + 1);
assert_eq!(a.get(), "alive");
drop(a);
}
#[test]
fn test_orphaned_block_on_other_thread() {
let mut arena = Arena::new();
let a = ArenaBox::new(alloc::string::String::from("a"), &mut arena);
let b = ArenaBox::new(alloc::string::String::from("b"), &mut arena);
drop(arena);
std::thread::spawn(move || {
assert_eq!(a.get(), "a");
drop(a);
assert_eq!(b.get(), "b");
drop(b);
})
.join()
.unwrap();
}
#[test]
fn test_orphaned_chunks() {
let rc = Arc::new(());
let mut arena = Arena::new();
let mut kept = Vec::new();
let mut boxes = Vec::new();
for idx in 0..1200 {
let b = ArenaBox::new((rc.clone(), [idx as u8; 32]), &mut arena);
if idx == 10 || idx == 900 || idx == 901 {
kept.push(b);
} else {
boxes.push(b);
}
}
let big = ArenaBox::new([7u8; 40000], &mut arena);
ArenaBox::release_in(big, &mut arena);
drop(boxes);
let orphaned_before = orphaned();
drop(arena);
assert_eq!(orphaned(), orphaned_before + 1);
assert_eq!(Arc::strong_count(&rc), 4);
for b in &kept {
assert_eq!(b.get().1[0], b.get().1[31]);
}
kept.swap(1, 2);
drop(kept);
assert_eq!(Arc::strong_count(&rc), 1);
}
#[test]
fn test_orphaned_in_other_arena() {
let mut arena = Arena::new();
let a = ArenaBox::new(1u64, &mut arena);
drop(arena);
let mut arena = Arena::new();
let b = ArenaBox::new(2u64, &mut arena);
ArenaBox::release_in(a, &mut arena);
assert!(!arena.is_empty());
assert_eq!(*b.get(), 2);
ArenaBox::release_in(b, &mut arena);
assert!(arena.is_empty());
}
#[test]
fn test_orphaned_concurrently() {
let rounds = if cfg!(miri) { 4 } else { 200 };
for _ in 0..rounds {
let rc = Arc::new(());
let mut arena = Arena::new();
let barrier = Arc::new(std::sync::Barrier::new(4));
let mut threads = Vec::new();
for thread in 0..3 {
let boxes = (0..if cfg!(miri) { 20 } else { 300 })
.map(|idx| ArenaBox::new((rc.clone(), [(thread + idx) as u8; 24]), &mut arena))
.collect::<Vec<_>>();
let barrier = barrier.clone();
threads.push(std::thread::spawn(move || {
barrier.wait();
for (idx, b) in boxes.into_iter().enumerate() {
assert_eq!(b.get().1[0], (thread + idx) as u8);
drop(b);
}
}));
}
barrier.wait();
drop(arena);
for thread in threads {
thread.join().unwrap();
}
assert_eq!(Arc::strong_count(&rc), 1);
}
}
}