#![allow(unsafe_code)]
#![allow(dead_code)]
use std::alloc::{GlobalAlloc, Layout, System};
use std::cell::Cell;
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
const MAX_ARENAS: usize = 128;
struct Region {
base: AtomicPtr<u8>,
end: AtomicUsize,
taken: AtomicBool,
}
#[allow(clippy::declare_interior_mutable_const)] const ZERO_REGION: Region = Region {
base: AtomicPtr::new(std::ptr::null_mut()),
end: AtomicUsize::new(0),
taken: AtomicBool::new(false),
};
static REGION_SLOTS: AtomicUsize = AtomicUsize::new(0);
static REGIONS: [Region; MAX_ARENAS] = [ZERO_REGION; MAX_ARENAS];
fn claim_slot(preferred: usize) -> Option<usize> {
if preferred != NO_SLOT
&& REGIONS[preferred]
.taken
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
return Some(preferred);
}
for (idx, region) in REGIONS.iter().enumerate() {
if region
.taken
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
REGION_SLOTS.fetch_max(idx + 1, Ordering::AcqRel);
return Some(idx);
}
}
None
}
fn release_slot(idx: usize) {
REGIONS[idx].taken.store(false, Ordering::Release);
}
#[inline]
fn in_any_arena(p: usize) -> bool {
let n = REGION_SLOTS.load(Ordering::Relaxed).min(MAX_ARENAS);
for r in ®IONS[..n] {
let base = r.base.load(Ordering::Acquire) as usize;
if base != 0 && p >= base && p < r.end.load(Ordering::Relaxed) {
return true;
}
}
false
}
fn bump_compute(cur: usize, align: usize, size: usize, end: usize) -> Option<(usize, usize)> {
let aligned = cur.checked_add(align - 1)? & !(align - 1);
let next = aligned.checked_add(size)?;
(next <= end).then_some((aligned, next))
}
#[cfg(target_arch = "wasm32")]
const fn parse_mb(s: &str) -> usize {
let bytes = s.as_bytes();
let mut n = 0usize;
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
assert!(
b >= b'0' && b <= b'9',
"SASSO_WASM_ARENA_MB must be decimal digits"
);
n = n * 10 + (b - b'0') as usize;
i += 1;
}
n
}
#[cfg(target_arch = "wasm32")]
const WASM_DEFAULT_ARENA_SIZE: usize = match option_env!("SASSO_WASM_ARENA_MB") {
Some(s) => parse_mb(s) * 1024 * 1024,
None => 32 * 1024 * 1024,
};
static ARENA_CONFIG: AtomicUsize = AtomicUsize::new(0);
pub fn set_arena_bytes(bytes: usize) {
ARENA_CONFIG.store(if bytes == 0 { usize::MAX } else { bytes }, Ordering::Relaxed);
}
#[cfg(target_arch = "wasm32")]
#[inline]
fn effective_arena_size() -> usize {
match ARENA_CONFIG.load(Ordering::Relaxed) {
0 => WASM_DEFAULT_ARENA_SIZE,
usize::MAX => 0,
n => n,
}
}
#[cfg(not(target_arch = "wasm32"))]
#[inline]
fn effective_arena_size() -> usize {
2 * 1024 * 1024 * 1024 }
struct ThreadState {
base: Cell<*mut u8>,
end: Cell<usize>,
cursor: Cell<usize>,
depth: Cell<u32>,
paused: Cell<u32>,
reserve_failed: Cell<bool>,
registry_full: Cell<bool>,
slot: Cell<usize>,
last_slot: Cell<usize>,
}
const NO_SLOT: usize = usize::MAX;
enum Reserved {
Yes,
Never,
NotNow,
}
impl ThreadState {
const fn new() -> ThreadState {
ThreadState {
base: Cell::new(std::ptr::null_mut()),
end: Cell::new(0),
cursor: Cell::new(0),
depth: Cell::new(0),
paused: Cell::new(0),
reserve_failed: Cell::new(false),
registry_full: Cell::new(false),
slot: Cell::new(NO_SLOT),
last_slot: Cell::new(NO_SLOT),
}
}
#[cold]
fn reserve(&self) -> Reserved {
let size = effective_arena_size();
if size == 0 {
return Reserved::Never; }
let Some(slot) = claim_slot(self.last_slot.get()) else {
return Reserved::NotNow; };
let region = ®IONS[slot];
let mut base = region.base.load(Ordering::Acquire);
if base.is_null() {
let Ok(layout) = Layout::from_size_align(size, 4096) else {
release_slot(slot);
return Reserved::Never;
};
let p = unsafe { System.alloc(layout) };
if p.is_null() {
release_slot(slot);
return Reserved::Never;
}
region.end.store(p as usize + size, Ordering::Relaxed);
region.base.store(p, Ordering::Release);
base = p;
}
self.slot.set(slot);
self.base.set(base);
self.end.set(region.end.load(Ordering::Relaxed));
self.cursor.set(base as usize);
Reserved::Yes
}
}
impl Drop for ThreadState {
fn drop(&mut self) {
let slot = self.slot.get();
if slot == NO_SLOT {
return;
}
self.slot.set(NO_SLOT);
self.base.set(std::ptr::null_mut());
self.end.set(0);
self.cursor.set(0);
release_slot(slot);
}
}
thread_local! {
static TL: ThreadState = const { ThreadState::new() };
}
pub struct ScopedAlloc;
unsafe impl GlobalAlloc for ScopedAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let Ok(p) = TL.try_with(|tl| {
if tl.depth.get() == 0 || tl.paused.get() > 0 {
return unsafe { System.alloc(layout) };
}
if tl.base.get().is_null() {
if tl.reserve_failed.get() || tl.registry_full.get() {
return unsafe { System.alloc(layout) };
}
match tl.reserve() {
Reserved::Yes => {}
Reserved::Never => {
tl.reserve_failed.set(true);
return unsafe { System.alloc(layout) };
}
Reserved::NotNow => {
tl.registry_full.set(true);
return unsafe { System.alloc(layout) };
}
}
}
match bump_compute(tl.cursor.get(), layout.align(), layout.size(), tl.end.get()) {
Some((aligned, next)) => {
tl.cursor.set(next);
let base = tl.base.get();
unsafe { base.add(aligned - base as usize) }
}
None => unsafe { System.alloc(layout) },
}
}) else {
return unsafe { System.alloc(layout) };
};
p
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
if !in_any_arena(ptr as usize) {
unsafe { System.dealloc(ptr, layout) };
}
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
let resized = TL.try_with(|tl| {
if tl.depth.get() == 0 || tl.paused.get() > 0 {
return false;
}
let base = tl.base.get();
if base.is_null() {
return false;
}
let addr = ptr as usize;
if addr < base as usize || addr + layout.size() != tl.cursor.get() {
return false;
}
match addr.checked_add(new_size) {
Some(new_end) if new_end <= tl.end.get() => {
tl.cursor.set(new_end);
true
}
_ => false,
}
});
if resized.unwrap_or(false) {
return ptr;
}
unsafe {
let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
let new_ptr = self.alloc(new_layout);
if !new_ptr.is_null() {
core::ptr::copy_nonoverlapping(ptr, new_ptr, layout.size().min(new_size));
self.dealloc(ptr, layout);
}
new_ptr
}
}
}
pub(crate) struct Scope;
impl Scope {
pub(crate) fn enter() -> Scope {
TL.with(|tl| {
tl.depth.set(tl.depth.get() + 1);
tl.registry_full.set(false);
});
Scope
}
}
impl Drop for Scope {
fn drop(&mut self) {
if leave_no_reset() {
reset();
}
}
}
pub(crate) fn leave_no_reset() -> bool {
TL.with(|tl| {
let d = tl.depth.get().saturating_sub(1);
tl.depth.set(d);
d == 0
})
}
pub(crate) fn reset() {
TL.with(|tl| {
if tl.depth.get() != 0 {
return;
}
let slot = tl.slot.get();
if slot == NO_SLOT {
tl.cursor.set(tl.base.get() as usize);
return;
}
tl.last_slot.set(slot);
tl.slot.set(NO_SLOT);
tl.base.set(std::ptr::null_mut());
tl.end.set(0);
tl.cursor.set(0);
release_slot(slot);
});
}
pub(crate) fn pause() -> Paused {
TL.with(|tl| tl.paused.set(tl.paused.get() + 1));
Paused
}
#[must_use]
pub(crate) struct Paused;
impl Drop for Paused {
fn drop(&mut self) {
TL.with(|tl| tl.paused.set(tl.paused.get().saturating_sub(1)));
}
}
#[cfg(test)]
struct Arena {
base: *mut u8,
size: usize,
end: usize,
cursor: Cell<usize>,
}
#[cfg(test)]
impl Arena {
fn with_system_backing(size: usize) -> Option<Arena> {
let layout = Layout::from_size_align(size, 4096).ok()?;
let base = unsafe { System.alloc(layout) };
if base.is_null() {
return None;
}
Some(Arena {
base,
size,
end: base as usize + size,
cursor: Cell::new(base as usize),
})
}
fn alloc(&self, layout: Layout) -> Option<*mut u8> {
let (aligned, next) = bump_compute(self.cursor.get(), layout.align(), layout.size(), self.end)?;
self.cursor.set(next);
Some(unsafe { self.base.add(aligned - self.base as usize) })
}
fn reset(&self) {
self.cursor.set(self.base as usize);
}
fn used(&self) -> usize {
self.cursor.get() - self.base as usize
}
fn contains(&self, ptr: *mut u8) -> bool {
let p = ptr as usize;
p >= self.base as usize && p < self.end
}
fn realloc(&self, ptr: *mut u8, old: Layout, new_size: usize) -> Option<*mut u8> {
let addr = ptr as usize;
if addr >= self.base as usize && addr + old.size() == self.cursor.get() {
let new_end = addr.checked_add(new_size)?;
if new_end <= self.end {
self.cursor.set(new_end);
return Some(ptr);
}
}
let np = self.alloc(Layout::from_size_align(new_size, old.align()).ok()?)?;
unsafe { core::ptr::copy_nonoverlapping(ptr, np, old.size().min(new_size)) };
Some(np)
}
}
#[cfg(test)]
impl Drop for Arena {
fn drop(&mut self) {
if let Ok(layout) = Layout::from_size_align(self.size, 4096) {
unsafe { System.dealloc(self.base, layout) };
}
}
}
#[cfg(test)]
mod tests {
use super::*;
static REGION_COUNT: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn compute_aligns_up() {
assert_eq!(bump_compute(10, 8, 4, 1000), Some((16, 20)));
assert_eq!(bump_compute(16, 8, 8, 1000), Some((16, 24)));
assert_eq!(bump_compute(7, 1, 3, 1000), Some((7, 10)));
}
#[test]
fn compute_every_power_of_two_alignment() {
for align in [1usize, 2, 4, 8, 16, 32, 64, 128, 256, 4096] {
let (aligned, next) = bump_compute(1, align, 64, usize::MAX).unwrap();
assert_eq!(aligned % align, 0, "align {align}");
assert_eq!(next, aligned + 64);
}
}
#[test]
fn compute_zero_size() {
assert_eq!(bump_compute(8, 8, 0, 100), Some((8, 8)));
}
#[test]
fn compute_boundary() {
assert_eq!(bump_compute(0, 1, 100, 100), Some((0, 100)));
assert_eq!(bump_compute(0, 1, 101, 100), None);
assert_eq!(bump_compute(90, 8, 20, 100), None);
}
#[test]
fn compute_overflow_is_none() {
assert_eq!(bump_compute(usize::MAX, 8, 0, usize::MAX), None);
assert_eq!(bump_compute(usize::MAX - 3, 1, 10, usize::MAX), None);
}
fn layout(size: usize, align: usize) -> Layout {
Layout::from_size_align(size, align).unwrap()
}
#[test]
fn realloc_extends_tail_in_place_else_copies() {
let a = Arena::with_system_backing(64 * 1024).unwrap();
let p = a.alloc(layout(8, 8)).unwrap();
unsafe { std::ptr::write_bytes(p, 0xCD, 8) };
let used = a.used();
let p2 = a.realloc(p, layout(8, 8), 16).unwrap();
assert_eq!(p, p2, "tail realloc grows in place");
assert_eq!(a.used(), used + 8, "only the +8 delta is consumed");
unsafe { assert_eq!(*p2, 0xCD, "data preserved in place") };
let _q = a.alloc(layout(8, 8)).unwrap();
let used_mid = a.used();
let p3 = a.realloc(p2, layout(16, 8), 32).unwrap();
assert_ne!(p2, p3, "non-tail realloc copies to a fresh block");
assert!(a.used() > used_mid, "fallback allocates fresh");
unsafe { assert_eq!(*p3, 0xCD, "data copied to the new block") };
}
#[test]
fn arena_alloc_is_aligned_writable_and_in_bounds() {
let a = Arena::with_system_backing(64 * 1024).unwrap();
for align in [1usize, 2, 4, 8, 16, 64, 256] {
let p = a.alloc(layout(128, align)).unwrap();
assert_eq!(p as usize % align, 0, "align {align}");
assert!(a.contains(p));
unsafe {
std::ptr::write_bytes(p, 0xAB, 128);
assert_eq!(*p, 0xAB);
assert_eq!(*p.add(127), 0xAB);
}
}
}
#[test]
fn arena_allocations_do_not_overlap() {
let a = Arena::with_system_backing(64 * 1024).unwrap();
let p1 = a.alloc(layout(64, 8)).unwrap() as usize;
let p2 = a.alloc(layout(64, 8)).unwrap() as usize;
assert!(p2 >= p1 + 64);
}
#[test]
fn arena_full_returns_none() {
let a = Arena::with_system_backing(4096).unwrap();
assert!(a.alloc(layout(8192, 8)).is_none());
assert!(a.alloc(layout(2048, 8)).is_some());
assert!(a.alloc(layout(2048, 8)).is_some());
assert!(a.alloc(layout(1, 1)).is_none());
}
#[test]
fn arena_reset_reuses_region() {
let a = Arena::with_system_backing(64 * 1024).unwrap();
let p1 = a.alloc(layout(1000, 8)).unwrap();
assert_eq!(a.used(), 1000);
a.reset();
assert_eq!(a.used(), 0);
let p2 = a.alloc(layout(1000, 8)).unwrap();
assert_eq!(p1, p2);
unsafe { std::ptr::write_bytes(p2, 0xCD, 1000) };
}
#[test]
#[cfg_attr(miri, ignore)]
fn scoped_routes_to_system_when_inactive() {
let _serial = REGION_COUNT.lock().unwrap_or_else(|e| e.into_inner());
let l = layout(64, 8);
let p = unsafe { ScopedAlloc.alloc(l) };
assert!(!p.is_null());
assert!(!TL.with(|tl| {
let b = tl.base.get() as usize;
(p as usize) >= b && (p as usize) < tl.end.get() && b != 0
}));
unsafe { ScopedAlloc.dealloc(p, l) };
}
#[test]
#[cfg_attr(miri, ignore)]
fn scoped_bumps_inside_scope_and_resets() {
let _serial = REGION_COUNT.lock().unwrap_or_else(|e| e.into_inner());
let l = layout(128, 16);
let scope = Scope::enter();
let p1 = unsafe { ScopedAlloc.alloc(l) };
let p2 = unsafe { ScopedAlloc.alloc(l) };
let in_arena = |p: *mut u8| {
TL.with(|tl| {
let b = tl.base.get() as usize;
b != 0 && (p as usize) >= b && (p as usize) < tl.end.get()
})
};
assert!(
in_arena(p1) && in_arena(p2),
"in-scope allocs come from the arena"
);
assert!(p2 as usize >= p1 as usize + 128, "no overlap");
assert_eq!(p1 as usize % 16, 0);
unsafe { ScopedAlloc.dealloc(p1, l) };
let outer = leave_no_reset();
assert!(outer);
reset();
let scope2 = Scope::enter();
let p3 = unsafe { ScopedAlloc.alloc(l) };
assert_eq!(p3, p1, "reset hands back the same region");
let _ = leave_no_reset();
reset();
drop(scope2);
std::mem::forget(scope);
}
#[test]
#[cfg_attr(miri, ignore)]
fn pause_routes_to_system_then_resumes() {
let _serial = REGION_COUNT.lock().unwrap_or_else(|e| e.into_inner());
let l = layout(64, 8);
let scope = Scope::enter();
let paused = pause(); let p_sys = unsafe { ScopedAlloc.alloc(l) }; let in_arena = |p: *mut u8| {
TL.with(|tl| {
let b = tl.base.get() as usize;
b != 0 && (p as usize) >= b && (p as usize) < tl.end.get()
})
};
assert!(!in_arena(p_sys), "paused scope routes to System");
unsafe { ScopedAlloc.dealloc(p_sys, l) };
drop(paused); let p_arena = unsafe { ScopedAlloc.alloc(l) };
assert!(in_arena(p_arena), "resumed scope bumps from the arena again");
let _ = leave_no_reset();
reset();
std::mem::forget(scope);
}
#[test]
#[cfg_attr(miri, ignore)]
fn nested_scope_while_paused_does_not_reset_outer_arena() {
let _serial = REGION_COUNT.lock().unwrap_or_else(|e| e.into_inner());
let l = layout(64, 8);
let outer = Scope::enter();
let p_outer = unsafe { ScopedAlloc.alloc(l) };
let cursor_before = TL.with(|tl| tl.cursor.get());
let paused = pause();
let inner = Scope::enter();
let p_inner = unsafe { ScopedAlloc.alloc(l) }; assert_ne!(p_inner, p_outer);
assert!(!leave_no_reset(), "nested scope is not the outermost");
reset(); std::mem::forget(inner);
unsafe { ScopedAlloc.dealloc(p_inner, l) };
drop(paused);
assert_eq!(
TL.with(|tl| tl.cursor.get()),
cursor_before,
"outer arena state intact"
);
let p_next = unsafe { ScopedAlloc.alloc(l) };
assert_ne!(p_next, p_outer, "the outer block was not handed out again");
let _ = leave_no_reset();
reset();
std::mem::forget(outer);
}
#[test]
#[cfg_attr(miri, ignore)]
fn threads_that_follow_one_another_share_one_region() {
let _serial = REGION_COUNT.lock().unwrap_or_else(|e| e.into_inner());
let mut bases = Vec::new();
for _ in 0..8 {
bases.push(
std::thread::spawn(|| {
let scope = Scope::enter();
let p = unsafe { ScopedAlloc.alloc(layout(64, 8)) };
assert!(in_any_arena(p as usize), "the thread got an arena");
let base = TL.with(|tl| tl.base.get() as usize);
let _ = leave_no_reset();
reset();
std::mem::forget(scope);
base
})
.join()
.unwrap(),
);
}
bases.dedup();
assert_eq!(
bases.len(),
1,
"each thread took a fresh region instead of reusing the free one",
);
}
#[test]
#[cfg_attr(miri, ignore)]
fn registry_slots_are_reusable_past_the_cap() {
let _serial = REGION_COUNT.lock().unwrap_or_else(|e| e.into_inner());
for _ in 0..(MAX_ARENAS + 8) {
std::thread::spawn(|| {
let scope = Scope::enter();
let p = unsafe { ScopedAlloc.alloc(layout(64, 8)) };
let arena = in_any_arena(p as usize);
let _ = leave_no_reset();
reset();
std::mem::forget(scope);
arena
})
.join()
.unwrap();
}
let got = std::thread::spawn(|| {
let scope = Scope::enter();
let p = unsafe { ScopedAlloc.alloc(layout(64, 8)) };
let arena = in_any_arena(p as usize);
let _ = leave_no_reset();
reset();
std::mem::forget(scope);
arena
})
.join()
.unwrap();
assert!(got, "a thread past MAX_ARENAS still bump-allocates");
}
#[test]
#[cfg_attr(miri, ignore)]
fn concurrent_threads_get_disjoint_regions_and_release_them() {
let _serial = REGION_COUNT.lock().unwrap_or_else(|e| e.into_inner());
let all_holding = std::sync::Arc::new(std::sync::Barrier::new(32));
let threads: Vec<_> = (0..32u8)
.map(|id| {
let all_holding = all_holding.clone();
std::thread::spawn(move || {
let scope = Scope::enter();
let l = layout(4096, 8);
let p = unsafe { ScopedAlloc.alloc(l) };
assert!(!p.is_null());
unsafe { std::ptr::write_bytes(p, id, 4096) };
std::thread::yield_now();
let seen = unsafe { std::slice::from_raw_parts(p, 4096) };
assert!(
seen.iter().all(|&b| b == id),
"another thread wrote into this thread's region",
);
let base = TL.with(|tl| tl.base.get() as usize);
all_holding.wait(); let _ = leave_no_reset();
reset();
std::mem::forget(scope);
base
})
})
.collect();
let mut bases: Vec<usize> = threads.into_iter().map(|t| t.join().unwrap()).collect();
let held = bases.len();
bases.sort_unstable();
bases.dedup();
assert_eq!(bases.len(), held, "two live threads shared a region");
let after = std::thread::spawn(|| {
let scope = Scope::enter();
let _ = unsafe { ScopedAlloc.alloc(layout(64, 8)) };
let base = TL.with(|tl| tl.base.get() as usize);
let _ = leave_no_reset();
reset();
std::mem::forget(scope);
base
})
.join()
.unwrap();
assert!(bases.contains(&after), "the released regions were not reused");
}
#[test]
#[cfg_attr(miri, ignore)]
fn allocating_while_the_thread_is_tearing_down_does_not_panic() {
let _serial = REGION_COUNT.lock().unwrap_or_else(|e| e.into_inner());
struct AllocsOnDrop;
impl Drop for AllocsOnDrop {
fn drop(&mut self) {
let l = layout(64, 8);
let p = unsafe { ScopedAlloc.alloc(l) };
assert!(!p.is_null(), "a teardown allocation must still succeed");
unsafe { ScopedAlloc.dealloc(p, l) };
}
}
thread_local! {
static LATE: std::cell::RefCell<Option<AllocsOnDrop>> =
const { std::cell::RefCell::new(None) };
}
std::thread::spawn(|| {
LATE.with(|l| *l.borrow_mut() = Some(AllocsOnDrop));
let scope = Scope::enter();
let _ = unsafe { ScopedAlloc.alloc(layout(64, 8)) };
let _ = leave_no_reset();
reset();
std::mem::forget(scope);
})
.join()
.expect("the thread tore down without panicking in the allocator");
}
#[test]
#[cfg_attr(miri, ignore)]
fn more_live_threads_than_slots_all_get_an_arena_in_turn() {
let _serial = REGION_COUNT.lock().unwrap_or_else(|e| e.into_inner());
let n = MAX_ARENAS + 2;
let turn = std::sync::Arc::new(std::sync::Mutex::new(()));
let done = std::sync::Arc::new(std::sync::Barrier::new(n + 1));
let threads: Vec<_> = (0..n)
.map(|_| {
let turn = turn.clone();
let done = done.clone();
std::thread::spawn(move || {
let got = {
let _one_at_a_time = turn.lock().unwrap_or_else(|e| e.into_inner());
let scope = Scope::enter();
let p = unsafe { ScopedAlloc.alloc(layout(64, 8)) };
let got = in_any_arena(p as usize);
let _ = leave_no_reset();
reset();
std::mem::forget(scope);
got
};
done.wait(); got
})
})
.collect();
done.wait();
let got: Vec<bool> = threads.into_iter().map(|t| t.join().unwrap()).collect();
assert_eq!(
got.iter().filter(|&&g| g).count(),
n,
"a live thread that had finished compiling was still holding its slot",
);
}
#[test]
#[cfg_attr(miri, ignore)]
fn a_thread_that_found_the_registry_full_recovers_when_a_slot_frees() {
let _serial = REGION_COUNT.lock().unwrap_or_else(|e| e.into_inner());
let hold = std::sync::Arc::new(std::sync::Barrier::new(MAX_ARENAS + 1));
let (seated_tx, seated_rx) = std::sync::mpsc::channel::<()>();
let holders: Vec<_> = (0..MAX_ARENAS)
.map(|_| {
let hold = hold.clone();
let seated = seated_tx.clone();
std::thread::spawn(move || {
let scope = Scope::enter();
let _ = unsafe { ScopedAlloc.alloc(layout(64, 8)) };
seated.send(()).unwrap();
hold.wait();
let _ = leave_no_reset();
reset();
std::mem::forget(scope);
})
})
.collect();
for _ in 0..MAX_ARENAS {
seated_rx.recv().unwrap();
}
let (answer_tx, answer_rx) = std::sync::mpsc::channel::<bool>();
let (go_tx, go_rx) = std::sync::mpsc::channel::<()>();
let probe = std::thread::spawn(move || {
let attempt = || {
let scope = Scope::enter();
let p = unsafe { ScopedAlloc.alloc(layout(64, 8)) };
let got = in_any_arena(p as usize);
let _ = leave_no_reset();
reset();
std::mem::forget(scope);
got
};
answer_tx.send(attempt()).unwrap();
go_rx.recv().unwrap();
answer_tx.send(attempt()).unwrap();
});
assert!(!answer_rx.recv().unwrap(), "no slot was free, so no arena");
hold.wait(); for h in holders {
h.join().unwrap();
}
go_tx.send(()).unwrap();
assert!(
answer_rx.recv().unwrap(),
"the same thread gets an arena once a slot comes back",
);
probe.join().unwrap();
}
#[test]
#[cfg_attr(miri, ignore)]
fn pause_guard_lifts_on_unwind() {
let before = TL.with(|tl| tl.paused.get());
let hook = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let result = std::panic::catch_unwind(|| {
let _paused = pause();
panic!("callback panicked");
});
std::panic::set_hook(hook);
assert!(result.is_err());
assert_eq!(TL.with(|tl| tl.paused.get()), before, "unwinding drops the guard");
}
}