pub struct SlabOwner<T, B: Allocator + FixedRange> { /* private fields */ }Expand description
Owns the slab. Has exclusive allocate access. Send (can be moved
across threads) but !Sync (one-at-a-time access — enforced by
UnsafeCell on the inner slab plus our manual Send impl with no
corresponding Sync impl).
§API-misuse compile-failures (pinned)
SlabOwner is !Sync by construction (_not_sync: PhantomData<Cell<()>>); a future refactor that accidentally
rederived Sync would let two threads share an &SlabOwner and race
on the UnsafeCell<Slab> inside inner.slab. The compile_fail
below pins that rejection:
// FAILS TO COMPILE: SlabOwner is deliberately !Sync. The
// `_not_sync: PhantomData<Cell<()>>` marker blocks the auto-derive,
// and `assert_sync` cannot accept a `!Sync` type.
use forge_alloc::InlineBacked;
use forge_alloc::SlabOwner;
fn assert_sync<T: Sync>() {}
assert_sync::<SlabOwner<u64, InlineBacked<512>>>();Implementations§
Source§impl<T, B: Allocator + FixedRange> SlabOwner<T, B>
impl<T, B: Allocator + FixedRange> SlabOwner<T, B>
Sourcepub fn new(capacity: usize, backing: B) -> Result<Self, AllocError>
pub fn new(capacity: usize, backing: B) -> Result<Self, AllocError>
Construct, taking ownership of a freshly-built slab.
Sourcepub fn with_batch_policy(
capacity: usize,
backing: B,
batch_policy: BatchPolicy,
queue_capacity: usize,
) -> Result<Self, AllocError>
pub fn with_batch_policy( capacity: usize, backing: B, batch_policy: BatchPolicy, queue_capacity: usize, ) -> Result<Self, AllocError>
Construct with explicit batch policy and queue capacity.
Sourcepub fn remote(&self) -> SlabRemote<T, B>
pub fn remote(&self) -> SlabRemote<T, B>
Create a remote handle. Cheap — just an Arc clone.
Sourcepub fn drain(&self)
pub fn drain(&self)
Drain the remote-free queue into the local freelist now.
Holds the queue mutex only long enough to swap out the pending
entries; releases the lock before calling slab.deallocate for
each entry. Without this two-phase pattern, remote senders would
be blocked through the entire drain loop — death by lock-hold time
proportional to queue depth.
Sourcepub fn adaptive_threshold_snapshot(&self) -> Option<usize>
pub fn adaptive_threshold_snapshot(&self) -> Option<usize>
Current adaptive-step threshold (in remote-queue entries), or
None if the owner is configured with BatchPolicy::Fixed.
Useful in tests and adaptive-tuning telemetry.
Trait Implementations§
Source§impl<T, B: Allocator + FixedRange> Allocator for SlabOwner<T, B>
impl<T, B: Allocator + FixedRange> Allocator for SlabOwner<T, B>
Source§fn allocate(&self, layout: NonZeroLayout) -> Result<NonNull<[u8]>, AllocError>
fn allocate(&self, layout: NonZeroLayout) -> Result<NonNull<[u8]>, AllocError>
layout. The returned slice’s length is
at least layout.size() but may be larger.Source§unsafe fn usable_size(
&self,
ptr: NonNull<u8>,
layout: NonZeroLayout,
) -> Option<usize>
unsafe fn usable_size( &self, ptr: NonNull<u8>, layout: NonZeroLayout, ) -> Option<usize>
None — implementors that track usable size
override. Read moreSource§fn capacity_bytes(&self) -> Option<usize>
fn capacity_bytes(&self) -> Option<usize>
None for unbounded
allocators like System. Used by Watermark to compute thresholds.Source§fn corruption_events(&self) -> u64
fn corruption_events(&self) -> u64
Source§fn allocate_zeroed(
&self,
layout: NonZeroLayout,
) -> Result<NonNull<[u8]>, AllocError>
fn allocate_zeroed( &self, layout: NonZeroLayout, ) -> Result<NonNull<[u8]>, AllocError>
Source§unsafe fn grow(
&self,
ptr: NonNull<u8>,
old: NonZeroLayout,
new: NonZeroLayout,
) -> Result<NonNull<[u8]>, AllocError>
unsafe fn grow( &self, ptr: NonNull<u8>, old: NonZeroLayout, new: NonZeroLayout, ) -> Result<NonNull<[u8]>, AllocError>
Source§unsafe fn shrink(
&self,
ptr: NonNull<u8>,
old: NonZeroLayout,
new: NonZeroLayout,
) -> Result<NonNull<[u8]>, AllocError>
unsafe fn shrink( &self, ptr: NonNull<u8>, old: NonZeroLayout, new: NonZeroLayout, ) -> Result<NonNull<[u8]>, AllocError>
Source§impl<T, B: Allocator + FixedRange> Deallocator for SlabOwner<T, B>
impl<T, B: Allocator + FixedRange> Deallocator for SlabOwner<T, B>
Source§unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: NonZeroLayout)
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: NonZeroLayout)
Source§impl<T, B: Allocator + FixedRange> Drop for SlabOwner<T, B>
Final drain on owner drop. Without this:
impl<T, B: Allocator + FixedRange> Drop for SlabOwner<T, B>
Final drain on owner drop. Without this:
SlabRemoteclones outliving the owner would push entries intoremote_queuethat nothing ever drains.- Slots queued for return (already routed by the remote, not yet
drained by the owner) would never be reclaimed back into the
slab’s local freelist. The slab keeps those slots marked-live
for as long as the last remote keeps the
Arc<SlabInner>alive — operationally a slot-table leak. Note thatT’s destructor is the remote caller’s responsibility BEFORE callingSlabRemote::deallocate(per theSlab::deallocatesafety contract); the drain here only reclaims the freelist entry, it does not runT::drop. - Subsequent
SlabRemote::deallocate(the spinning, infallible variant) would spin forever on a full queue, hanging the calling thread.
We close the queue while holding its mutex (race-free against any
in-flight remote push) and drain the pending entries into the local
freelist. After this, the slab is consistent and any further remote
push observes closed == true and returns Err(ptr) /
no-ops without spinning.