use core::ptr::NonNull;
use crate::error::{AllocError, FreeError};
use crate::stats::AllocatorStats;
use crate::{InitError, global_allocator, try_with_thread_cache};
pub fn allocate(size: usize) -> Result<NonNull<u8>, AllocError> {
try_with_thread_cache(|allocator, cache| allocator.allocate_with_cache(cache, size))
.map_err(|_| AllocError::GlobalInitFailed)?
.ok_or(AllocError::GlobalInitFailed)?
}
pub unsafe fn deallocate(ptr: NonNull<u8>) -> Result<(), FreeError> {
try_with_thread_cache(|allocator, cache| {
unsafe { allocator.deallocate_with_cache(cache, ptr) }
})
.map_err(|_| FreeError::GlobalInitFailed)?
.ok_or(FreeError::GlobalInitFailed)?
}
pub unsafe fn deallocate_with_size(ptr: NonNull<u8>, size: usize) -> Result<(), FreeError> {
try_with_thread_cache(|allocator, cache| {
unsafe { allocator.deallocate_with_size_checked(cache, ptr, size) }
})
.map_err(|_| FreeError::GlobalInitFailed)?
.ok_or(FreeError::GlobalInitFailed)?
}
pub fn global_stats() -> Result<AllocatorStats, &'static InitError> {
let allocator = global_allocator()?;
Ok(allocator.stats())
}
#[cfg(test)]
mod tests {
use super::{allocate, deallocate};
use crate::error::{AllocError, FreeError};
use crate::try_with_thread_cache;
use std::cell::RefCell;
use std::sync::{Arc, Mutex};
struct AllocateDuringTlsTeardown {
result: Arc<Mutex<Option<Result<(), AllocError>>>>,
}
impl Drop for AllocateDuringTlsTeardown {
fn drop(&mut self) {
let outcome = allocate(32).map(|_| ());
*self.result.lock().unwrap_or_else(|error| {
panic!("expected teardown result lock to succeed: {error}")
}) = Some(outcome);
}
}
thread_local! {
static TEARDOWN_PROBE: RefCell<Option<AllocateDuringTlsTeardown>> = const { RefCell::new(None) };
}
fn reentrant_allocate_result() -> Result<core::ptr::NonNull<u8>, AllocError> {
try_with_thread_cache(|_, _| allocate(32))
.unwrap_or_else(|error| panic!("expected global allocator init to succeed: {error}"))
.unwrap_or_else(|| panic!("expected outer thread-cache borrow to succeed"))
}
fn reentrant_deallocate_result() -> Result<(), FreeError> {
let ptr = allocate(32)
.unwrap_or_else(|error| panic!("expected initial allocation to succeed: {error}"));
let outcome = try_with_thread_cache(|_, _| {
unsafe { deallocate(ptr) }
})
.unwrap_or_else(|error| panic!("expected global allocator init to succeed: {error}"))
.unwrap_or_else(|| panic!("expected outer thread-cache borrow to succeed"));
unsafe {
deallocate(ptr).unwrap_or_else(|error| {
panic!("expected cleanup deallocation to succeed: {error}")
});
}
outcome
}
#[test]
fn public_allocate_returns_typed_error_when_thread_cache_is_reentrantly_borrowed() {
assert_eq!(
reentrant_allocate_result(),
Err(AllocError::GlobalInitFailed)
);
}
#[test]
fn public_deallocate_returns_typed_error_when_thread_cache_is_reentrantly_borrowed() {
assert_eq!(
reentrant_deallocate_result(),
Err(FreeError::GlobalInitFailed)
);
}
#[test]
fn public_allocate_does_not_panic_when_thread_cache_is_reentrantly_borrowed() {
let outcome = std::panic::catch_unwind(reentrant_allocate_result);
assert!(
outcome.is_ok(),
"public allocate should return an error instead of panicking on reentrant thread-cache borrow"
);
}
#[test]
fn public_deallocate_does_not_panic_when_thread_cache_is_reentrantly_borrowed() {
let outcome = std::panic::catch_unwind(reentrant_deallocate_result);
assert!(
outcome.is_ok(),
"public deallocate should return an error instead of panicking on reentrant thread-cache borrow"
);
}
#[test]
fn public_allocate_returns_typed_error_after_thread_cache_tls_is_destroyed() {
let teardown_result = Arc::new(Mutex::new(None));
let thread_result = Arc::clone(&teardown_result);
let handle = std::thread::spawn(move || {
TEARDOWN_PROBE.with(|probe| {
*probe.borrow_mut() = Some(AllocateDuringTlsTeardown {
result: thread_result,
});
});
let ptr = allocate(32)
.unwrap_or_else(|error| panic!("expected initial allocation to succeed: {error}"));
unsafe {
deallocate(ptr).unwrap_or_else(|error| {
panic!("expected cleanup deallocation to succeed: {error}")
});
}
});
handle
.join()
.unwrap_or_else(|payload| std::panic::resume_unwind(payload));
let outcome = teardown_result
.lock()
.unwrap_or_else(|error| panic!("expected teardown result lock to succeed: {error}"))
.take()
.unwrap_or_else(|| panic!("expected teardown probe to record a result"));
assert_eq!(outcome, Err(AllocError::GlobalInitFailed));
}
}