| // Copyright 2020 The Fuchsia Authors | |
| // | |
| // Use of this source code is governed by a MIT-style | |
| // license that can be found in the LICENSE file or at | |
| // https://opensource.org/licenses/MIT | |
| //! Power-of-two slab object cache allocator. | |
| //! | |
| //! `ObjectCache` retains freed slabs up to a configurable reserve limit to reduce contention on | |
| //! the underlying slab allocator. Both single-cache (`Single`) and per-CPU cache (`PerCpu`) modes | |
| //! are supported. | |
| //! | |
| //! # Custom Allocators | |
| //! | |
| //! Custom slab allocators may be either globally stateful (`GlobalSlabAllocator`, stateless value) | |
| //! or locally stateful (`SlabAllocator`, carrying per-instance state). Slabs must have a | |
| //! power-of-two `SLAB_SIZE` and be aligned to `SLAB_SIZE`. | |
| //! | |
| //! # Orphan Slab Support | |
| //! | |
| //! An `ObjectCache` using a `GlobalSlabAllocator` can be dropped before all objects allocated from | |
| //! it have been freed. Active slabs are marked as orphans when the cache is dropped, and each | |
| //! orphan slab frees itself back to the global allocator once its last allocated object is freed. | |
| mod ffi; | |
| use crate::arch_rs::curr_cpu_num; | |
| use crate::counters::define_kcounter; | |
| use crate::kernel::percpu::PerCpu as KernelPerCpu; | |
| use crate::kernel::thread::{AutoPreemptDisabler, current_memory_allocation_state_is_enabled}; | |
| use crate::kernel::types::VAddr; | |
| use crate::ksync_kernel::TrackedMutex; | |
| use crate::vm::page_state::VmPageState; | |
| use crate::vm::page_state::bindings::vm_page_state; | |
| use crate::vm::{physmap, pmm}; | |
| use core::convert::Infallible; | |
| use core::marker::PhantomData; | |
| use core::mem::{ManuallyDrop, MaybeUninit, align_of, size_of}; | |
| use core::ops::{Deref, DerefMut}; | |
| use core::pin::Pin; | |
| use core::ptr::NonNull; | |
| use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; | |
| use fbl::{ | |
| Array, Canary, DefaultObjectTag, DoublyLinkedList, DoublyLinkedListContainable, | |
| DoublyLinkedListNode, HasRefCount, ManagedPtr, MutPtr, PtrTraits, Recyclable, RefCounted, | |
| RefPtr, TrackingSize, magic, | |
| }; | |
| use ksync::{KCell, KMutex}; | |
| use pin_init::{Init, PinInit, init_from_closure, pin_data, pin_init, pinned_drop}; | |
| use zx_status::Status; | |
| const TRACE_LEVEL: i32 = 0; | |
| const BASIC: i32 = 1; | |
| const DETAIL: i32 = 2; | |
| define_kcounter!(CACHE_OBJECT_ALLOCATED, "cache.object.allocated", Sum); | |
| define_kcounter!(CACHE_SLAB_ALLOCATED, "cache.slab.allocated", Sum); | |
| define_kcounter!(CACHE_OBJECT_FREED, "cache.object.freed", Sum); | |
| define_kcounter!(CACHE_SLAB_FREED, "cache.slab.freed", Sum); | |
| fn count_object_allocation() { | |
| CACHE_OBJECT_ALLOCATED.add(1); | |
| } | |
| fn count_slab_allocation() { | |
| CACHE_SLAB_ALLOCATED.add(1); | |
| } | |
| fn count_object_free() { | |
| CACHE_OBJECT_FREED.add(1); | |
| } | |
| fn count_slab_free() { | |
| CACHE_SLAB_FREED.add(1); | |
| } | |
| fn get_processor_count() -> usize { | |
| KernelPerCpu::processor_count() | |
| } | |
| /// Trait for slab allocators (potentially carrying per-instance state) backing an `ObjectCache`. | |
| /// | |
| /// # Safety | |
| /// | |
| /// Implementers must guarantee: | |
| /// - `SLAB_SIZE` is a power of two. | |
| /// - `allocate` returns a valid, writable, exclusively owned block of at least `SLAB_SIZE` | |
| /// bytes aligned to `SLAB_SIZE`. | |
| /// - `IS_STATEFUL` accurately indicates whether `Slab::destroy` must dereference | |
| /// `object_cache` (`true`, meaning slabs cannot outlive their `ObjectCache`) or may run on | |
| /// an orphan slab after the cache is dropped (`false`, using `release_global`). | |
| pub unsafe trait SlabAllocator { | |
| /// Size of each slab in bytes. Must be a power of two. | |
| const SLAB_SIZE: usize; | |
| /// True if this allocator carries per-instance state and cannot outlive its `ObjectCache`. | |
| const IS_STATEFUL: bool = true; | |
| /// Allocates a new slab aligned to `Self::SLAB_SIZE`. | |
| fn allocate(&self) -> Result<NonNull<u8>, Status>; | |
| /// Releases a slab previously returned by `allocate`. | |
| /// | |
| /// # Safety | |
| /// | |
| /// `slab` must be the base address of a slab allocated from `self` that is no longer in use. | |
| unsafe fn release(&self, slab: NonNull<u8>); | |
| /// Increments the object allocation counter. | |
| fn count_object_allocation(&self); | |
| /// Increments the object free counter. | |
| fn count_object_free(&self); | |
| /// Increments the slab allocation counter. | |
| fn count_slab_allocation(&self); | |
| /// Increments the slab free counter. | |
| fn count_slab_free(&self); | |
| /// Releases an orphan slab without an `ObjectCache` instance. | |
| /// | |
| /// # Safety | |
| /// | |
| /// `slab` must be the base address of an orphan slab allocated from this global allocator. | |
| unsafe fn release_global(_slab: NonNull<u8>) { | |
| unreachable!("stateful allocator cannot release orphan slabs"); | |
| } | |
| /// Increments the object free counter for an orphan slab. | |
| fn count_object_free_global() { | |
| unreachable!("stateful allocator cannot free objects on orphan slabs"); | |
| } | |
| /// Increments the slab free counter for an orphan slab. | |
| fn count_slab_free_global() { | |
| unreachable!("stateful allocator cannot free orphan slabs"); | |
| } | |
| } | |
| /// Trait for stateless / globally stateful slab allocators that support orphan slabs. | |
| /// | |
| /// # Safety | |
| /// | |
| /// Implementers must guarantee: | |
| /// - `SLAB_SIZE` is a power of two. | |
| /// - `allocate` returns a valid, writable, exclusively owned block of at least `SLAB_SIZE` | |
| /// bytes aligned to `SLAB_SIZE`. | |
| /// - Slabs allocated by `allocate` can be freed via `release` without an `ObjectCache` | |
| /// instance, allowing orphan slabs to outlive the cache. | |
| pub unsafe trait GlobalSlabAllocator: Default { | |
| /// Size of each slab in bytes. Must be a power of two. | |
| const SLAB_SIZE: usize; | |
| /// Allocates a new slab aligned to `Self::SLAB_SIZE`. | |
| fn allocate() -> Result<NonNull<u8>, Status>; | |
| /// Releases a slab previously returned by `allocate`. | |
| /// | |
| /// # Safety | |
| /// | |
| /// `slab` must be the base address of a slab allocated from `Self` that is no longer in use. | |
| unsafe fn release(slab: NonNull<u8>); | |
| /// Increments the object allocation counter. | |
| fn count_object_allocation(); | |
| /// Increments the object free counter. | |
| fn count_object_free(); | |
| /// Increments the slab allocation counter. | |
| fn count_slab_allocation(); | |
| /// Increments the slab free counter. | |
| fn count_slab_free(); | |
| } | |
| // SAFETY: `G` upholds the `GlobalSlabAllocator` alignment, size, and validity invariants, and | |
| // stateless / globally stateful allocators support orphan slabs (`IS_STATEFUL == false`). | |
| unsafe impl<G: GlobalSlabAllocator> SlabAllocator for G { | |
| const SLAB_SIZE: usize = G::SLAB_SIZE; | |
| const IS_STATEFUL: bool = false; | |
| #[inline] | |
| fn allocate(&self) -> Result<NonNull<u8>, Status> { | |
| G::allocate() | |
| } | |
| /// # Safety | |
| /// | |
| /// `slab` must be the base address of a slab allocated from `G` that is no longer in use. | |
| #[inline] | |
| unsafe fn release(&self, slab: NonNull<u8>) { | |
| // SAFETY: Caller upholds the `SlabAllocator::release` contract. | |
| unsafe { G::release(slab) }; | |
| } | |
| #[inline] | |
| fn count_object_allocation(&self) { | |
| G::count_object_allocation(); | |
| } | |
| #[inline] | |
| fn count_object_free(&self) { | |
| G::count_object_free(); | |
| } | |
| #[inline] | |
| fn count_slab_allocation(&self) { | |
| G::count_slab_allocation(); | |
| } | |
| #[inline] | |
| fn count_slab_free(&self) { | |
| G::count_slab_free(); | |
| } | |
| /// # Safety | |
| /// | |
| /// `slab` must be the base address of an orphan slab allocated from `G`. | |
| #[inline] | |
| unsafe fn release_global(slab: NonNull<u8>) { | |
| // SAFETY: Caller upholds the `SlabAllocator::release_global` contract. | |
| unsafe { G::release(slab) }; | |
| } | |
| #[inline] | |
| fn count_object_free_global() { | |
| G::count_object_free(); | |
| } | |
| #[inline] | |
| fn count_slab_free_global() { | |
| G::count_slab_free(); | |
| } | |
| } | |
| /// The default allocator for `ObjectCache`. Allocates page-sized slabs from the PMM. | |
| /// | |
| /// This allocator is globally stateful, allowing orphan slabs. | |
| #[derive(Debug, Default, Clone, Copy)] | |
| pub struct DefaultAllocator; | |
| impl DefaultAllocator { | |
| /// Size of each slab in bytes. | |
| pub const SLAB_SIZE: usize = page::SIZE; | |
| /// Allocates a page-sized slab from the PMM. | |
| pub fn allocate() -> Result<NonNull<u8>, Status> { | |
| let (vm_page, paddr) = pmm::alloc_page(pmm::ALLOC_FLAG_ANY)?; | |
| // SAFETY: `vm_page` was just allocated from the PMM and we have exclusive access to it. | |
| unsafe { | |
| vm_page.as_ref().set_state(VmPageState(vm_page_state::SLAB)); | |
| } | |
| let vaddr = physmap::paddr_to_physmap(paddr); | |
| let ptr = core::ptr::with_exposed_provenance_mut::<u8>(vaddr.0); | |
| // SAFETY: `paddr_to_physmap` returns a valid non-null kernel virtual address. | |
| Ok(unsafe { NonNull::new_unchecked(ptr) }) | |
| } | |
| /// Releases a slab previously returned by `DefaultAllocator::allocate`. | |
| /// | |
| /// # Safety | |
| /// | |
| /// `slab` must be the base address of a slab allocated from `DefaultAllocator` and no | |
| /// longer in use. | |
| pub unsafe fn release(slab: NonNull<u8>) { | |
| let paddr = physmap::physmap_to_paddr(VAddr(slab.addr().get())); | |
| let page = pmm::paddr_to_vm_page(paddr).expect("slab maps to valid vm_page"); | |
| // SAFETY: `page` is the valid SLAB page being freed back to the PMM. | |
| unsafe { | |
| debug_assert_eq!(page.as_ref().state(), VmPageState(vm_page_state::SLAB)); | |
| pmm::free_page(page); | |
| } | |
| } | |
| /// Increments the object allocation counter. | |
| pub fn count_object_allocation() { | |
| count_object_allocation(); | |
| } | |
| /// Increments the object free counter. | |
| pub fn count_object_free() { | |
| count_object_free(); | |
| } | |
| /// Increments the slab allocation counter. | |
| pub fn count_slab_allocation() { | |
| count_slab_allocation(); | |
| } | |
| /// Increments the slab free counter. | |
| pub fn count_slab_free() { | |
| count_slab_free(); | |
| } | |
| } | |
| // SAFETY: `DefaultAllocator::SLAB_SIZE` (`page::SIZE`) is a power of two, `allocate` returns a | |
| // page-aligned writable page from the PMM, and `release` frees it globally via the PMM. | |
| unsafe impl GlobalSlabAllocator for DefaultAllocator { | |
| const SLAB_SIZE: usize = Self::SLAB_SIZE; | |
| fn allocate() -> Result<NonNull<u8>, Status> { | |
| Self::allocate() | |
| } | |
| /// # Safety | |
| /// | |
| /// `slab` must be the base address of a slab allocated from `DefaultAllocator` that is no | |
| /// longer in use. | |
| unsafe fn release(slab: NonNull<u8>) { | |
| // SAFETY: Caller upholds the `GlobalSlabAllocator::release` contract. | |
| unsafe { Self::release(slab) }; | |
| } | |
| fn count_object_allocation() { | |
| Self::count_object_allocation(); | |
| } | |
| fn count_object_free() { | |
| Self::count_object_free(); | |
| } | |
| fn count_slab_allocation() { | |
| Self::count_slab_allocation(); | |
| } | |
| fn count_slab_free() { | |
| Self::count_slab_free(); | |
| } | |
| } | |
| /// Mode marker for a single shared `ObjectCache`. | |
| #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] | |
| pub struct Single; | |
| /// Mode marker for a per-CPU `ObjectCache`. | |
| #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] | |
| pub struct PerCpu; | |
| mod sealed { | |
| use super::SlabAllocator; | |
| pub trait Sealed<T, A: SlabAllocator> {} | |
| } | |
| /// Trait mapping cache mode markers (`Single`, `PerCpu`) to their internal representation. | |
| pub trait CacheMode<T, A: SlabAllocator>: sealed::Sealed<T, A> { | |
| /// Internal cache storage type for this mode. | |
| type Inner; | |
| } | |
| impl<T, A: SlabAllocator> sealed::Sealed<T, A> for Single {} | |
| impl<T, A: SlabAllocator> CacheMode<T, A> for Single { | |
| type Inner = SingleCache<T, A>; | |
| } | |
| impl<T, A: SlabAllocator> sealed::Sealed<T, A> for PerCpu {} | |
| impl<T, A: SlabAllocator> CacheMode<T, A> for PerCpu { | |
| type Inner = PerCpuCache<T, A>; | |
| } | |
| /// Owned smart pointer to an object allocated from an `ObjectCache<T, _, A>`. | |
| /// | |
| /// Dropping `UniquePtr<T, A>` drops `T` in place and returns its slab slot to the slab it was | |
| /// allocated from. | |
| #[repr(transparent)] | |
| pub struct UniquePtr<T, A: SlabAllocator = DefaultAllocator> { | |
| ptr: NonNull<T>, | |
| _phantom: PhantomData<A>, | |
| } | |
| // SAFETY: `UniquePtr` uniquely owns `T`, and dropping it on another thread is safe when `T: Send` | |
| // and `A: Send + Sync`. | |
| unsafe impl<T: Send, A: SlabAllocator + Send + Sync> Send for UniquePtr<T, A> {} | |
| // SAFETY: `UniquePtr` only hands out `&T` from `&self`, so it is `Sync` when `T: Sync` and | |
| // `A: Send + Sync`. | |
| unsafe impl<T: Sync, A: SlabAllocator + Send + Sync> Sync for UniquePtr<T, A> {} | |
| impl<T, A: SlabAllocator> UniquePtr<T, A> { | |
| /// Returns a const raw pointer to the managed object. | |
| #[inline] | |
| pub fn as_ptr(this: &Self) -> *const T { | |
| this.ptr.as_ptr() | |
| } | |
| /// Returns a mutable raw pointer to the managed object. | |
| #[inline] | |
| pub fn as_mut_ptr(this: &mut Self) -> *mut T { | |
| this.ptr.as_ptr() | |
| } | |
| /// Consumes the `UniquePtr`, returning the raw pointer without destroying the object. | |
| #[inline] | |
| pub fn into_raw(this: Self) -> *mut T { | |
| let ptr = this.ptr.as_ptr(); | |
| core::mem::forget(this); | |
| ptr | |
| } | |
| /// Reconstructs a `UniquePtr` from a raw pointer previously returned by `into_raw`. | |
| /// | |
| /// # Safety | |
| /// | |
| /// `ptr` must be a valid, uniquely owned pointer to a `T` allocated from an | |
| /// `ObjectCache<T, _, A>`. | |
| #[inline] | |
| pub unsafe fn from_raw(ptr: *mut T) -> Self { | |
| // SAFETY: Caller guarantees `ptr` is non-null and valid. | |
| Self { ptr: unsafe { NonNull::new_unchecked(ptr) }, _phantom: PhantomData } | |
| } | |
| /// Adopts a freshly allocated ref-counted object into a `fbl::RefPtr<T>`. | |
| #[inline] | |
| pub fn into_ref_ptr(this: Self) -> RefPtr<T> | |
| where | |
| T: HasRefCount + ObjectCacheDeletable<A>, | |
| { | |
| let raw = Self::into_raw(this); | |
| // SAFETY: `raw` points to a freshly constructed, unadopted `T`, and | |
| // `T: ObjectCacheDeletable<A>` guarantees its `Recyclable::recycle` implementation | |
| // drops `T` in place and returns its slot to `ObjectCache<T, Single, A>` via | |
| // `delete_raw`. | |
| unsafe { RefPtr::adopt_raw(raw) } | |
| } | |
| } | |
| impl<T, A: SlabAllocator> Deref for UniquePtr<T, A> { | |
| type Target = T; | |
| #[inline] | |
| fn deref(&self) -> &Self::Target { | |
| // SAFETY: `self.ptr` points to a live, initialized `T` owned by `self`. | |
| unsafe { self.ptr.as_ref() } | |
| } | |
| } | |
| impl<T, A: SlabAllocator> DerefMut for UniquePtr<T, A> { | |
| #[inline] | |
| fn deref_mut(&mut self) -> &mut Self::Target { | |
| // SAFETY: `self` has exclusive ownership of `self.ptr`. | |
| unsafe { self.ptr.as_mut() } | |
| } | |
| } | |
| impl<T, A: SlabAllocator> Drop for UniquePtr<T, A> { | |
| fn drop(&mut self) { | |
| // SAFETY: `self.ptr` is uniquely owned by `self` and was allocated from an | |
| // `ObjectCache<T, _, A>`. | |
| unsafe { | |
| core::ptr::drop_in_place(self.ptr.as_ptr()); | |
| SingleCache::<T, A>::delete_raw(self.ptr); | |
| } | |
| } | |
| } | |
| // SAFETY: `UniquePtr<T, A>` uniquely owns `T` and round-trips losslessly through `into_raw` and | |
| // `from_raw`. | |
| unsafe impl<T, A: SlabAllocator> PtrTraits for UniquePtr<T, A> { | |
| type Target = T; | |
| const IS_MANAGED: bool = true; | |
| #[inline] | |
| fn into_raw(self) -> *mut T { | |
| Self::into_raw(self) | |
| } | |
| /// # Safety | |
| /// | |
| /// `raw` must have been returned by a previous call to `UniquePtr::into_raw`. | |
| #[inline] | |
| unsafe fn from_raw(raw: *mut T) -> Self { | |
| // SAFETY: Caller guarantees `raw` came from `into_raw`. | |
| unsafe { Self::from_raw(raw) } | |
| } | |
| #[inline] | |
| fn get_ref(&self) -> &T { | |
| self | |
| } | |
| } | |
| // SAFETY: `UniquePtr<T, A>` manages the lifetime of `T` and drops it when dropped. | |
| unsafe impl<T, A: SlabAllocator> ManagedPtr for UniquePtr<T, A> {} | |
| // SAFETY: `UniquePtr<T, A>` holds exclusive ownership of `T`. | |
| unsafe impl<T, A: SlabAllocator> MutPtr for UniquePtr<T, A> {} | |
| /// Marker trait for types whose [`fbl::Recyclable::recycle`] implementation returns the object to | |
| /// an `ObjectCache<Self, Single, A>`. | |
| /// | |
| /// This is the Rust equivalent of C++ `object_cache::Deletable<T, Allocator>`. Use | |
| /// [`impl_object_cache_deletable!`] to implement both [`fbl::Recyclable`] and | |
| /// `ObjectCacheDeletable<A>` for a type. | |
| /// | |
| /// # Safety | |
| /// | |
| /// Implementers must guarantee that `<Self as fbl::Recyclable>::recycle` drops `Self` in place and | |
| /// returns the backing slot to `ObjectCache<Self, Single, A>` via [`ObjectCache::delete_raw`]. | |
| pub unsafe trait ObjectCacheDeletable<A: SlabAllocator = DefaultAllocator>: | |
| Recyclable | |
| { | |
| } | |
| /// Implements [`fbl::Recyclable`] and [`ObjectCacheDeletable`] for a type allocated from an | |
| /// `ObjectCache` so it can be held in `fbl::RefPtr<T>`. | |
| /// | |
| /// This is the Rust equivalent of C++ `object_cache::Deletable<T, Allocator>`. | |
| #[macro_export] | |
| macro_rules! impl_object_cache_deletable { | |
| ($ty:ty) => { | |
| $crate::impl_object_cache_deletable!($ty, $crate::object_cache::DefaultAllocator); | |
| }; | |
| ($ty:ty, $alloc:ty) => { | |
| // SAFETY: `$ty` instances are allocated from `ObjectCache<$ty, _, $alloc>`, so dropping | |
| // in place and calling `ObjectCache::delete_raw` is the matching deallocation path. | |
| unsafe impl ::fbl::Recyclable for $ty { | |
| /// # Safety | |
| /// | |
| /// `ptr` must be the last reference to a valid, initialized `Self` allocated from an | |
| /// `ObjectCache<Self, _, $alloc>`. | |
| unsafe fn recycle(ptr: ::core::ptr::NonNull<Self>) { | |
| type Cache<T> = | |
| $crate::object_cache::ObjectCache<T, $crate::object_cache::Single, $alloc>; | |
| // SAFETY: `ptr` is the last reference to a valid, initialized `Self` allocated | |
| // from an `ObjectCache<Self, _, $alloc>`. | |
| unsafe { | |
| ::core::ptr::drop_in_place(ptr.as_ptr()); | |
| Cache::<Self>::delete_raw(ptr); | |
| } | |
| } | |
| } | |
| // SAFETY: The `Recyclable::recycle` implementation above drops `Self` in place and returns | |
| // the slot to `ObjectCache<Self, Single, $alloc>` via `ObjectCache::delete_raw`. | |
| unsafe impl $crate::object_cache::ObjectCacheDeletable<$alloc> for $ty {} | |
| }; | |
| } | |
| pub use impl_object_cache_deletable; | |
| // Optimization flags that impact the efficiency of the Slab destructor. May | |
| // be set to `false` to force manually clearing the free list for debugging | |
| // purposes. | |
| const ENTRY_ALLOW_CLEAR_UNSAFE: bool = true; | |
| // An entry in the Slab object array. Exists either as a node in the slab free | |
| // list or as an allocated instance of T. | |
| #[repr(C)] | |
| union Entry<T> { | |
| list_node: ManuallyDrop<DoublyLinkedListNode<Entry<T>>>, | |
| object: ManuallyDrop<MaybeUninit<T>>, | |
| } | |
| impl<T> DoublyLinkedListContainable<Entry<T>> for Entry<T> { | |
| #[inline] | |
| fn get_node(&self) -> &DoublyLinkedListNode<Entry<T>> { | |
| // SAFETY: `get_node` is only called while the entry is linked in `SlabControl::free_list`, | |
| // where `list_node` is the active union field. | |
| unsafe { &self.list_node } | |
| } | |
| } | |
| impl<T> Entry<T> { | |
| /// Initializes this entry's free-list node in uninitialized slab memory. | |
| /// | |
| /// # Safety | |
| /// | |
| /// `entry` must point to valid, aligned, uninitialized storage in the slab entry array. | |
| unsafe fn init_list_node(entry: NonNull<Self>) { | |
| // SAFETY: `entry` points to valid, uninitialized storage in the slab entry array. | |
| unsafe { | |
| (&raw mut (*entry.as_ptr()).list_node) | |
| .write(ManuallyDrop::new(DoublyLinkedListNode::new())); | |
| } | |
| } | |
| /// Converts this entry from a list node to an object, constructing the instance of `T` with | |
| /// the given initializer. This should be called after the entry is allocated, outside of | |
| /// internal locks to avoid unnecessary lock dependencies. | |
| /// | |
| /// # Safety | |
| /// | |
| /// `slot` must point to an entry just popped from `free_list` with an active `list_node`. | |
| unsafe fn to_object_init<E>( | |
| slot: NonNull<Self>, | |
| init: impl Init<T, E>, | |
| ) -> Result<NonNull<T>, E> { | |
| let _trace = crate::ktrace_rs::begin_scope_cond!( | |
| TRACE_LEVEL >= DETAIL, | |
| "kernel:sched", | |
| "Entry::ToObject" | |
| ); | |
| // SAFETY: `slot` was just popped from `free_list`, so `list_node` is initialized and not | |
| // in any container. | |
| unsafe { | |
| ManuallyDrop::drop(&mut (*slot.as_ptr()).list_node); | |
| } | |
| let object_ptr = slot.cast::<T>(); | |
| // SAFETY: `object_ptr` points to valid, aligned, uninitialized storage for `T`. | |
| unsafe { | |
| init.__init(object_ptr.as_ptr())?; | |
| } | |
| Ok(object_ptr) | |
| } | |
| /// Converts this entry to a list node. The object must already be destroyed. | |
| /// | |
| /// # Safety | |
| /// | |
| /// `pointer` must point to a valid slab entry slot whose `T` value has already been dropped. | |
| unsafe fn to_list_node(pointer: NonNull<T>) -> NonNull<Self> { | |
| let _trace = crate::ktrace_rs::begin_scope_cond!( | |
| TRACE_LEVEL >= DETAIL, | |
| "kernel:sched", | |
| "Entry::ToListNode" | |
| ); | |
| let entry = pointer.cast::<Self>(); | |
| // SAFETY: `entry` points to valid, unborrowed storage in the slab entry array. | |
| unsafe { | |
| (&raw mut (*entry.as_ptr()).list_node) | |
| .write(ManuallyDrop::new(DoublyLinkedListNode::new())); | |
| } | |
| entry | |
| } | |
| } | |
| type FreeList<T> = DoublyLinkedList<NonNull<Entry<T>>, DefaultObjectTag, TrackingSize>; | |
| type SlabList<T, A> = DoublyLinkedList<RefPtr<Slab<T, A>>>; | |
| // Slab control block. Separate from the Entry array to simplify computing the | |
| // size of the Entry array and to avoid aliasing `&Slab` with `&mut T` entries. | |
| #[ksync::guarded] | |
| #[pin_data] | |
| #[repr(C)] | |
| struct SlabControl<T, A: SlabAllocator> { | |
| canary: Canary<{ magic(b"slab") }>, | |
| ref_count: RefCounted, | |
| #[pin] | |
| list_node: DoublyLinkedListNode<Slab<T, A>>, | |
| orphan_flag: AtomicBool, | |
| #[mutex] | |
| lock: KMutex<TrackedMutex>, | |
| #[guarded_by(lock)] | |
| object_cache: NonNull<SingleCache<T, A>>, | |
| #[pin] | |
| #[guarded_by(lock)] | |
| free_list: FreeList<T>, | |
| } | |
| // A slab of objects in the object cache. Constructed at `base + CONTROL_OFFSET` | |
| // inside a power-of-two aligned memory block of size `A::SLAB_SIZE`, with | |
| // `ENTRIES_PER_SLAB` `Entry<T>` slots placed at `base + 0 .. base + CONTROL_OFFSET`. | |
| #[pin_data(PinnedDrop)] | |
| #[repr(C)] | |
| struct Slab<T, A: SlabAllocator> { | |
| #[pin] | |
| control: SlabControl<T, A>, | |
| } | |
| // SAFETY: `Slab` synchronizes all mutable access through `control.lock` and atomics. | |
| unsafe impl<T: Send, A: SlabAllocator + Send + Sync> Send for Slab<T, A> {} | |
| // SAFETY: `Slab` synchronizes all shared access through `control.lock` and atomics. | |
| unsafe impl<T: Send, A: SlabAllocator + Send + Sync> Sync for Slab<T, A> {} | |
| impl<T, A: SlabAllocator> HasRefCount for Slab<T, A> { | |
| #[inline] | |
| fn ref_count(&self) -> &RefCounted { | |
| &self.control.ref_count | |
| } | |
| } | |
| impl<T, A: SlabAllocator> DoublyLinkedListContainable<Slab<T, A>> for Slab<T, A> { | |
| #[inline] | |
| fn get_node(&self) -> &DoublyLinkedListNode<Slab<T, A>> { | |
| &self.control.list_node | |
| } | |
| } | |
| // SAFETY: `Slab` is allocated via `A::allocate` and destroyed by `Slab::destroy`. | |
| unsafe impl<T, A: SlabAllocator> Recyclable for Slab<T, A> { | |
| /// # Safety | |
| /// | |
| /// `ptr` must be the last `RefPtr` reference to this `Slab`. | |
| #[inline] | |
| unsafe fn recycle(ptr: NonNull<Self>) { | |
| // SAFETY: `ptr` is the last `RefPtr` reference to this `Slab`. | |
| unsafe { Self::destroy(ptr) }; | |
| } | |
| } | |
| #[pinned_drop] | |
| impl<T, A: SlabAllocator> PinnedDrop for Slab<T, A> { | |
| fn drop(self: Pin<&mut Self>) { | |
| let _trace = crate::ktrace_rs::begin_scope_cond!( | |
| TRACE_LEVEL >= DETAIL, | |
| "kernel:sched", | |
| "Slab::~Slab" | |
| ); | |
| let entries_start = Self::base_ptr(self.as_ref().get_ref()).cast::<Entry<T>>(); | |
| let mut free_list = self.project().control.project().free_list.get_pinned_mut(); | |
| debug_assert!(Self::is_empty(&free_list)); | |
| if ENTRY_ALLOW_CLEAR_UNSAFE { | |
| // SAFETY: This slab is being destroyed after the last reference was released. Every | |
| // entry's `list_node` is wrapped in `ManuallyDrop` inside the `Entry` union and is | |
| // never dropped or used in any list or cursor operation again before `Slab::destroy` | |
| // releases the backing slab memory immediately after `drop`. | |
| unsafe { | |
| free_list.clear_unsafe(); | |
| } | |
| } else { | |
| // Consistency check that every entry is on the free list. Attempting to | |
| // erase an entry that is not on the free list will assert. | |
| for i in 0..Self::ENTRIES_PER_SLAB { | |
| // SAFETY: `i < Self::ENTRIES_PER_SLAB`, so `entry_ptr` points to an `Entry<T>` | |
| // slot within `[base, base + CONTROL_OFFSET)`. Every allocated object holds a | |
| // reference to its slab, so by the time the last reference is dropped every entry | |
| // has been returned to this slab's `free_list` (debug-checked by `is_empty()` | |
| // above); its active field is therefore `list_node`, which is linked in | |
| // `free_list`. After erasing, the node is unlinked and never used again, so | |
| // dropping it is sound. | |
| unsafe { | |
| let entry_ptr = entries_start.add(i); | |
| let erased = free_list.as_mut().erase(&*entry_ptr); | |
| debug_assert!(erased.is_some()); | |
| ManuallyDrop::drop(&mut (*entry_ptr).list_node); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| impl<T, A: SlabAllocator> Slab<T, A> { | |
| const SLAB_ADDR_MASK: usize = A::SLAB_SIZE - 1; | |
| const SLAB_CONTROL_SIZE: usize = | |
| size_of::<SlabControl<T, A>>().next_multiple_of(align_of::<SlabControl<T, A>>()); | |
| const ENTRIES_PER_SLAB: usize = | |
| (A::SLAB_SIZE - Self::SLAB_CONTROL_SIZE) / size_of::<Entry<T>>(); | |
| const CONTROL_OFFSET: usize = | |
| (Self::ENTRIES_PER_SLAB * size_of::<Entry<T>>()).next_multiple_of(align_of::<Self>()); | |
| const _LAYOUT_ASSERT: () = { | |
| assert!(A::SLAB_SIZE.is_power_of_two(), "Slabs must be a power of two!"); | |
| assert!(A::SLAB_SIZE > Self::SLAB_CONTROL_SIZE); | |
| assert!(Self::ENTRIES_PER_SLAB > 0); | |
| assert!(align_of::<Entry<T>>() <= A::SLAB_SIZE); | |
| assert!(align_of::<Self>() <= A::SLAB_SIZE); | |
| assert!(Self::CONTROL_OFFSET + size_of::<Self>() <= A::SLAB_SIZE); | |
| }; | |
| // Allocates and initializes a new slab for `object_cache`. | |
| fn try_new(object_cache: NonNull<SingleCache<T, A>>, base: NonNull<u8>) -> RefPtr<Self> { | |
| let () = Self::_LAYOUT_ASSERT; | |
| let _trace = crate::ktrace_rs::begin_scope_cond!( | |
| TRACE_LEVEL >= DETAIL, | |
| "kernel:sched", | |
| "Slab::Slab" | |
| ); | |
| debug_assert_eq!(base.addr().get() & Self::SLAB_ADDR_MASK, 0); | |
| // Expose the full-slab allocation provenance so `base_ptr`, `destroy`, and | |
| // `from_allocated_pointer` can recover provenance over the entire `[base, base + | |
| // A::SLAB_SIZE)` block from any pointer into the slab. | |
| let _ = base.as_ptr().expose_provenance(); | |
| let slab_ptr = | |
| // SAFETY: `CONTROL_OFFSET + size_of::<Self>() <= A::SLAB_SIZE` is checked at compile | |
| // time, and `base` is aligned to `A::SLAB_SIZE >= align_of::<Self>()`. | |
| unsafe { base.as_ptr().add(Self::CONTROL_OFFSET) }.cast::<Self>(); | |
| let init = pin_init!(Self { | |
| control <- pin_init!(SlabControl { | |
| canary: Canary::new(), | |
| ref_count: RefCounted::new(), | |
| list_node: DoublyLinkedListNode::new(), | |
| orphan_flag: AtomicBool::new(false), | |
| lock <- KMutex::init(), | |
| object_cache: KCell::new(object_cache), | |
| free_list <- KCell::pin_init(FreeList::new()), | |
| }), | |
| }); | |
| // SAFETY: `slab_ptr` points to valid, aligned, uninitialized memory that stays pinned in | |
| // place until `Slab::destroy` drops it. | |
| unsafe { | |
| let _ = init.__pinned_init(slab_ptr); | |
| } | |
| // SAFETY: `slab_ptr` was just initialized above and no other thread can see it yet. | |
| let mut slab_pin = unsafe { Pin::new_unchecked(&mut *slab_ptr) }; | |
| let mut free_list = | |
| slab_pin.as_mut().project().control.project().free_list.get_pinned_mut(); | |
| for i in 0..Self::ENTRIES_PER_SLAB { | |
| // SAFETY: `i < Self::ENTRIES_PER_SLAB`, so `entry` lies within `[base, base + | |
| // CONTROL_OFFSET)` and does not overlap `slab_ptr`. | |
| let entry = unsafe { base.cast::<Entry<T>>().add(i) }; | |
| // SAFETY: `entry` is valid uninitialized slot memory in the slab. | |
| unsafe { | |
| Entry::init_list_node(entry); | |
| free_list.as_mut().push_front_raw(entry); | |
| } | |
| } | |
| // SAFETY: `slab_ptr` is freshly initialized and has not been adopted yet. | |
| unsafe { RefPtr::adopt_raw(slab_ptr) } | |
| } | |
| // Recovers the power-of-two aligned base pointer of the slab from a `Slab` pointer. | |
| #[inline] | |
| fn base_ptr(slab: *const Self) -> *mut u8 { | |
| core::ptr::with_exposed_provenance_mut::<u8>(slab.addr() & !Self::SLAB_ADDR_MASK) | |
| } | |
| /// Destructs the slab and returns its memory to the allocator. | |
| /// | |
| /// # Safety | |
| /// | |
| /// `ptr` must point to a valid `Slab` whose last `RefPtr` reference is being released. | |
| unsafe fn destroy(ptr: NonNull<Self>) { | |
| let _trace = crate::ktrace_rs::begin_scope_cond!( | |
| TRACE_LEVEL >= DETAIL, | |
| "kernel:sched", | |
| "Slab::Destroy" | |
| ); | |
| let slab_ptr = core::ptr::with_exposed_provenance_mut::<Self>(ptr.addr().get()); | |
| // SAFETY: `ptr` is non-null, so `slab_ptr` with the same address is non-null. | |
| let slab_nonnull = unsafe { NonNull::new_unchecked(slab_ptr) }; | |
| // SAFETY: `slab_nonnull` is valid and uniquely owned by `destroy` when the last `RefPtr` | |
| // drops. | |
| let slab_ref = unsafe { slab_nonnull.as_ref() }; | |
| // Slabs cannot be orphaned when the allocator is stateful. | |
| debug_assert!(!A::IS_STATEFUL || !slab_ref.is_orphan()); | |
| // The lock should not be held or acquired when the slab is being destroyed. | |
| // SAFETY: `slab_ptr` is valid and uniquely owned here, with full-slab mutable provenance. | |
| let object_cache = unsafe { | |
| let mut slab_pin = Pin::new_unchecked(&mut *slab_ptr); | |
| *slab_pin.as_mut().project().control.project().object_cache.get_inner_mut() | |
| }; | |
| let base_raw = Self::base_ptr(slab_ptr); | |
| // SAFETY: `slab_ptr` is valid and uniquely owned; `PinnedDrop` checks `is_empty()` and | |
| // empties `free_list` via `clear_unsafe` (or the per-entry erase loop when | |
| // `ENTRY_ALLOW_CLEAR_UNSAFE` is false), and `TrackedMutex` drop asserts the lock is not | |
| // held. | |
| unsafe { | |
| core::ptr::drop_in_place(slab_ptr); | |
| } | |
| // SAFETY: `base_raw` is the non-null aligned base of this slab. | |
| let base = unsafe { NonNull::new_unchecked(base_raw) }; | |
| if A::IS_STATEFUL { | |
| // SAFETY: Stateful allocators never orphan slabs, so `object_cache` outlives `Slab`. | |
| let cache = unsafe { object_cache.as_ref() }; | |
| cache.allocator.count_slab_free(); | |
| // SAFETY: `base` was allocated from `cache.allocator`. | |
| unsafe { | |
| cache.allocator.release(base); | |
| } | |
| } else { | |
| A::count_slab_free_global(); | |
| // SAFETY: `base` was allocated from global allocator `A`. | |
| unsafe { | |
| A::release_global(base); | |
| } | |
| } | |
| } | |
| #[inline] | |
| fn in_container(&self) -> bool { | |
| self.control.list_node.in_container() | |
| } | |
| fn set_orphan(&self) { | |
| let _trace = crate::ktrace_rs::begin_scope_cond!( | |
| TRACE_LEVEL >= DETAIL, | |
| "kernel:sched", | |
| "Slab::SetOrphan" | |
| ); | |
| self.control.orphan_flag.store(true, Ordering::SeqCst); | |
| } | |
| #[inline] | |
| fn available_objects(free_list: &FreeList<T>) -> usize { | |
| free_list.len() | |
| } | |
| #[inline] | |
| fn is_empty(free_list: &FreeList<T>) -> bool { | |
| Self::available_objects(free_list) == Self::ENTRIES_PER_SLAB | |
| } | |
| #[inline] | |
| fn is_full(free_list: &FreeList<T>) -> bool { | |
| free_list.is_empty() | |
| } | |
| #[inline] | |
| fn is_orphan(&self) -> bool { | |
| self.control.orphan_flag.load(Ordering::SeqCst) | |
| } | |
| /// Returns a reference to a slab given a pointer to an entry. | |
| /// | |
| /// # Safety | |
| /// | |
| /// `pointer` must point to an allocated entry slot inside a live `Slab<T, A>`. | |
| unsafe fn from_allocated_pointer(pointer: NonNull<T>) -> RefPtr<Self> { | |
| // Slab addresses are guaranteed to be power-of-two aligned. This contract | |
| // is checked in `try_new`. | |
| let slab_addr = (pointer.addr().get() & !Self::SLAB_ADDR_MASK) + Self::CONTROL_OFFSET; | |
| let slab_ptr = core::ptr::with_exposed_provenance_mut::<Self>(slab_addr); | |
| // SAFETY: `pointer` was allocated from this slab, which kept a live reference count on | |
| // `slab_ptr` via `RefPtr::add_ref` in `Slab::allocate`. | |
| unsafe { | |
| (*slab_ptr).control.canary.assert(); | |
| RefPtr::from_raw(slab_ptr) | |
| } | |
| } | |
| // Allocates an entry from the slab and moves the slab to the appropriate | |
| // list in the cache. The caller must hold a reference to this slab. | |
| fn allocate(&self) -> Result<NonNull<Entry<T>>, Status> { | |
| let _trace = crate::ktrace_rs::begin_scope_cond!( | |
| TRACE_LEVEL >= DETAIL, | |
| "kernel:sched", | |
| "Slab::Allocate" | |
| ); | |
| ksync::lock!(let mut slab_guard = self.control.lock_lock()); | |
| debug_assert!(!self.is_orphan()); | |
| // Retry on another slab if another thread allocated the last object in | |
| // this slab between releasing the object cache lock in `get_slab` and | |
| // acquiring the slab lock here. | |
| if Self::available_objects(slab_guard.fields().free_list) == 0 { | |
| return Err(Status::NO_MEMORY); | |
| } | |
| // SAFETY: `!self.is_orphan()` holds under `self.control.lock`, so `object_cache` is alive. | |
| let object_cache = unsafe { slab_guard.fields().object_cache.as_ref() }; | |
| ksync::lock!(let mut cache_guard = object_cache.lock_lock()); | |
| // Retry on another slab if another thread removed this slab from the | |
| // object cache between releasing the object cache lock in `get_slab` and | |
| // acquiring the slab lock here. Technically, this slab could be re-added | |
| // to the cache, however, there is a good chance that another partial slab | |
| // exists that would reduce fragmentation. | |
| if !self.in_container() { | |
| return Err(Status::NO_MEMORY); | |
| } | |
| let mut slab_fields = slab_guard.as_mut().fields_mut(); | |
| let was_empty = Self::is_empty(&slab_fields.free_list); | |
| let entry = slab_fields.free_list.as_mut().pop_front().expect("free_list not empty"); | |
| let is_full = Self::is_full(&slab_fields.free_list); | |
| if was_empty || is_full { | |
| let mut cache_fields = cache_guard.as_mut().fields_mut(); | |
| let erased = if was_empty { | |
| // SAFETY: `self` was empty and in a container, so it is in `empty_list`. | |
| unsafe { cache_fields.empty_list.as_mut().erase(self) } | |
| } else { | |
| // SAFETY: `self` was partial and in a container, so it is in `partial_list`. | |
| unsafe { cache_fields.partial_list.as_mut().erase(self) } | |
| } | |
| .expect("slab in source list"); | |
| if is_full { | |
| cache_fields.full_list.as_mut().push_front(erased); | |
| } else { | |
| cache_fields.partial_list.as_mut().push_front(erased); | |
| } | |
| } | |
| // The allocated object maintains a reference to prevent prematurely | |
| // releasing the slab in back reference scenarios. | |
| RefPtr::add_ref(self); | |
| object_cache.allocator.count_object_allocation(); | |
| Ok(entry) | |
| } | |
| // Returns the given entry to the free list. The caller must hold a | |
| // reference to the slab. | |
| fn free(&self, entry: NonNull<Entry<T>>) { | |
| let _trace = crate::ktrace_rs::begin_scope_cond!( | |
| TRACE_LEVEL >= DETAIL, | |
| "kernel:sched", | |
| "Slab::Free" | |
| ); | |
| let entries_start = Self::base_ptr(self).cast::<Entry<T>>(); | |
| // SAFETY: `ENTRIES_PER_SLAB` entries fit within the slab allocation. | |
| let entries_end = unsafe { entries_start.add(Self::ENTRIES_PER_SLAB) }; | |
| let entry_ptr = entry.as_ptr(); | |
| debug_assert!(entry_ptr >= entries_start && entry_ptr < entries_end); | |
| ksync::lock!(let mut control_guard = self.control.lock_lock()); | |
| debug_assert!( | |
| Self::available_objects(control_guard.fields().free_list) < Self::ENTRIES_PER_SLAB | |
| ); | |
| // If the cache containing this slab was destroyed while destroying the | |
| // last object in the cache (e.g. back reference) this slab will be marked | |
| // orphan and should not attempt to access the object cache. | |
| if self.is_orphan() { | |
| // Just return the entry to the free list and release the additional | |
| // reference to the orphan slab. | |
| // SAFETY: `entry` is a valid freed entry in this slab and not currently in any list. | |
| unsafe { | |
| control_guard.as_mut().fields_mut().free_list.as_mut().push_front_raw(entry); | |
| } | |
| A::count_object_free_global(); | |
| } else { | |
| // Our ObjectCache instance was alive while we held our lock, and our | |
| // slab must be a member of either the full or partial lists (meaning | |
| // that an ObjectCache in the process of destructing will need to bounce | |
| // through our lock on its way out). | |
| // | |
| // Increment the free operation in-flight count in our OC. This | |
| // guarantees that we will have a chance to fully release our OC's lock | |
| // and properly synchronize with the OC destructor in the case that we | |
| // end up moving our slab to the free list, or removing it entirely from | |
| // the object cache, just before we drop the lock. Disable preemption | |
| // while we do this. We don't want the destructor spinning waiting for | |
| // us to drop the in-flight count if we become preempted during this | |
| // operation. | |
| // SAFETY: `!self.is_orphan()` holds under `self.control.lock`, so `object_cache` is | |
| // alive and valid. | |
| let object_cache = unsafe { control_guard.fields().object_cache.as_ref() }; | |
| object_cache.canary.assert(); | |
| let _preempt_disable = AutoPreemptDisabler::new(); | |
| object_cache.slab_free_ops_in_flight.fetch_add(1, Ordering::Release); | |
| { | |
| // Explicit scope for the OC guard. | |
| ksync::lock!(let mut cache_guard = object_cache.lock_lock()); | |
| let was_full = Self::is_full(control_guard.fields().free_list); | |
| // SAFETY: `entry` is a valid freed entry in this slab and not in any list. | |
| unsafe { | |
| control_guard.as_mut().fields_mut().free_list.as_mut().push_front_raw(entry); | |
| } | |
| let is_empty = Self::is_empty(control_guard.fields().free_list); | |
| // This slab may have been orphaned while blocking on the cache lock | |
| // above if the cache destructor ran concurrently with this free | |
| // operation. | |
| if !self.is_orphan() { | |
| let mut cache_fields = cache_guard.as_mut().fields_mut(); | |
| if was_full || is_empty { | |
| let erased = if was_full { | |
| // SAFETY: `self` was full and not orphaned, so it is in `full_list`. | |
| unsafe { cache_fields.full_list.as_mut().erase(self) } | |
| } else { | |
| // SAFETY: `self` was partial and not orphaned, so it is in | |
| // `partial_list`. | |
| unsafe { cache_fields.partial_list.as_mut().erase(self) } | |
| } | |
| .expect("slab in source list"); | |
| if is_empty { | |
| cache_fields.empty_list.as_mut().push_front(erased); | |
| } else { | |
| cache_fields.partial_list.as_mut().push_front(erased); | |
| } | |
| } | |
| if is_empty && object_cache.should_trim_locked(&cache_fields) { | |
| SingleCache::remove_slab_locked(&mut cache_fields, self); | |
| } | |
| } | |
| } | |
| object_cache.allocator.count_object_free(); | |
| // Drop the in-flight count back to what it was, and never attempt to | |
| // touch our object cache again. It might already be in the process of | |
| // destructing. | |
| let prev = object_cache.slab_free_ops_in_flight.fetch_sub(1, Ordering::Release); | |
| debug_assert_ne!(prev, 0); | |
| } | |
| } | |
| } | |
| /// Internal implementation of `ObjectCache<T, Single, A>`. | |
| #[ksync::guarded] | |
| #[pin_data(PinnedDrop)] | |
| pub struct SingleCache<T, A: SlabAllocator = DefaultAllocator> { | |
| canary: Canary<{ magic(b"obj$") }>, | |
| reserve_slabs: usize, | |
| slab_free_ops_in_flight: AtomicU32, | |
| #[mutex] | |
| lock: KMutex<TrackedMutex>, | |
| #[guarded_by(lock)] | |
| slab_count: usize, | |
| // Lists of slabs in the object cache with the following functions: | |
| // - The partial list contains slabs with some allocated objects and some | |
| // entries in the free list. | |
| // - The empty list contains retained slabs with no allocated objects and all | |
| // entries in the free list. | |
| // - The full list contains slabs with all objects allocated and no entries | |
| // in the free list. | |
| // | |
| // Allocation from slabs in the partial list is preferred over the empty list | |
| // to reduce fragmentation. | |
| #[pin] | |
| #[guarded_by(lock)] | |
| partial_list: SlabList<T, A>, | |
| #[pin] | |
| #[guarded_by(lock)] | |
| empty_list: SlabList<T, A>, | |
| #[pin] | |
| #[guarded_by(lock)] | |
| full_list: SlabList<T, A>, | |
| // Placed last so `allocator` outlives the slab lists during field drop. | |
| allocator: A, | |
| } | |
| // SAFETY: `SingleCache` synchronizes all mutable state via `lock` and atomics. | |
| unsafe impl<T: Send, A: SlabAllocator + Send + Sync> Send for SingleCache<T, A> {} | |
| // SAFETY: `SingleCache` synchronizes all shared access via `lock` and atomics. | |
| unsafe impl<T: Send, A: SlabAllocator + Send + Sync> Sync for SingleCache<T, A> {} | |
| #[pinned_drop] | |
| impl<T, A: SlabAllocator> PinnedDrop for SingleCache<T, A> { | |
| fn drop(self: Pin<&mut Self>) { | |
| let _trace = crate::ktrace_rs::begin_scope_cond!( | |
| TRACE_LEVEL >= DETAIL, | |
| "kernel:sched", | |
| "ObjectCache::~ObjectCache" | |
| ); | |
| let this = self.as_ref().get_ref(); | |
| let (full_list_ptr, partial_list_ptr) = { | |
| ksync::lock!(let guard = this.lock_lock()); | |
| let fields = guard.fields(); | |
| // Slabs cannot be orphaned if the allocator is stateful, since the | |
| // allocator state is destroyed when this destructor completes. | |
| debug_assert!(!A::IS_STATEFUL || fields.full_list.is_empty()); | |
| debug_assert!(!A::IS_STATEFUL || fields.partial_list.is_empty()); | |
| // Mark active slabs orphan. Threads racing in `Slab::free` may not observe | |
| // this state before attempting to acquire the cache lock. | |
| for slab in fields.full_list.iter() { | |
| slab.set_orphan(); | |
| } | |
| for slab in fields.partial_list.iter() { | |
| slab.set_orphan(); | |
| } | |
| let token = guard.token(); | |
| // SAFETY: `guard` holds `this.lock`, and after all slabs in `full_list` and | |
| // `partial_list` are marked orphan above, no thread in `Slab::free` will | |
| // mutate `full_list` or `partial_list`. | |
| unsafe { (this.full_list.as_ptr(token), this.partial_list.as_ptr(token)) } | |
| }; | |
| // Wait for any threads racing in `Slab::free` to release their slab locks to | |
| // ensure that the cache lock is not destroyed while they hold it. | |
| // SAFETY: All slabs in `full_list` and `partial_list` were marked orphan under | |
| // `this.lock` above, so no thread in `Slab::free` will mutate either list. | |
| for slab in unsafe { (*full_list_ptr).iter() } { | |
| ksync::lock!(let _guard = slab.control.lock_lock()); | |
| } | |
| for slab in unsafe { (*partial_list_ptr).iter() } { | |
| ksync::lock!(let _guard = slab.control.lock_lock()); | |
| } | |
| // Finally, wait until the `slab_free_ops_in_flight` count hits zero before | |
| // calling `self.project()` (which forms `&mut AtomicU32` on | |
| // `slab_free_ops_in_flight`). There is an extremely small chance that there is | |
| // a thread still in the process of releasing our `lock` which we contested | |
| // during the "orphan" pass above. If we failed to synchronize with this thread | |
| // (because we had already moved our slab to the free list, or removed it | |
| // entirely), the `slab_free_ops_in_flight` count provides us one last way to | |
| // make sure that this thread has completely exited our lock before we allow it | |
| // to become destructed. Acq/Rel semantics here guarantee that we will properly | |
| // see the full release of the lock as we destruct. | |
| while this.slab_free_ops_in_flight.load(Ordering::Acquire) != 0 { | |
| libarch::r#yield(); | |
| } | |
| // Clear all slab lists explicitly while `self` and `self.allocator` are still live. | |
| let mut projected = self.project(); | |
| let mut full_list = projected.full_list.as_mut().get_pinned_mut(); | |
| let mut empty_list = projected.empty_list.as_mut().get_pinned_mut(); | |
| let mut partial_list = projected.partial_list.as_mut().get_pinned_mut(); | |
| full_list.as_mut().clear(); | |
| empty_list.as_mut().clear(); | |
| partial_list.as_mut().clear(); | |
| } | |
| } | |
| impl<T, A: SlabAllocator> SingleCache<T, A> { | |
| fn new_with(reserve_slabs: usize, allocator: A) -> impl PinInit<Self, Infallible> { | |
| let () = Slab::<T, A>::_LAYOUT_ASSERT; | |
| let _trace = crate::ktrace_rs::begin_scope_cond!( | |
| TRACE_LEVEL >= DETAIL, | |
| "kernel:sched", | |
| "ObjectCache::ObjectCache" | |
| ); | |
| pin_init!(Self { | |
| canary: Canary::new(), | |
| reserve_slabs, | |
| slab_free_ops_in_flight: AtomicU32::new(0), | |
| lock <- KMutex::init(), | |
| slab_count: KCell::new(0), | |
| partial_list <- KCell::pin_init(SlabList::new()), | |
| empty_list <- KCell::pin_init(SlabList::new()), | |
| full_list <- KCell::pin_init(SlabList::new()), | |
| allocator, | |
| }) | |
| } | |
| fn allocate_init<E>(&self, init: impl Init<T, E>) -> Result<UniquePtr<T, A>, E> | |
| where | |
| E: From<Status>, | |
| { | |
| let _trace = crate::ktrace_rs::begin_scope_cond!( | |
| TRACE_LEVEL >= BASIC, | |
| "kernel:sched", | |
| "ObjectCache::Allocate" | |
| ); | |
| debug_assert!(current_memory_allocation_state_is_enabled()); | |
| let _preempt_disable = AutoPreemptDisabler::new(); | |
| loop { | |
| let slab = self.get_slab().map_err(E::from)?; | |
| // Allocation can fail if one or more allocating threads race with a | |
| // thread that is either filling or releasing a slab. Retry on another | |
| // slab until allocation succeeds or slab allocation fails due to | |
| // insufficient memory. | |
| if let Ok(entry) = slab.allocate() { | |
| // SAFETY: `entry` was just allocated from `slab`, and we construct `T` outside | |
| // internal locks. If `init` fails, we return the uninitialized slot to the slab. | |
| match unsafe { Entry::to_object_init(entry, init) } { | |
| Ok(object_ptr) => { | |
| return Ok(UniquePtr { ptr: object_ptr, _phantom: PhantomData }); | |
| } | |
| Err(err) => { | |
| // SAFETY: `entry` has no constructed `T`; `delete_raw` reinitializes its | |
| // list node and returns it to `slab`. | |
| unsafe { | |
| Self::delete_raw(entry.cast::<T>()); | |
| } | |
| return Err(err); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| fn slab_count(&self) -> usize { | |
| ksync::lock!(let guard = self.lock_lock()); | |
| *guard.fields().slab_count | |
| } | |
| /// Returns the given object that has already been destroyed to the slab it was allocated from. | |
| /// | |
| /// # Safety | |
| /// | |
| /// `pointer` must point to a slot allocated from a `Slab<T, A>` whose `T` value has already | |
| /// been destroyed. | |
| unsafe fn delete_raw(pointer: NonNull<T>) { | |
| let _trace = crate::ktrace_rs::begin_scope_cond!( | |
| TRACE_LEVEL >= BASIC, | |
| "kernel:sched", | |
| "ObjectCache::Release" | |
| ); | |
| let _preempt_disable = AutoPreemptDisabler::new(); | |
| // SAFETY: `pointer` was allocated from a `Slab<T, A>`, and `from_allocated_pointer` | |
| // reclaims the object's reference to the slab. | |
| let slab = unsafe { Slab::<T, A>::from_allocated_pointer(pointer) }; | |
| // SAFETY: The caller already destroyed `*pointer`, and we exclusively own its slot. | |
| let entry = unsafe { Entry::<T>::to_list_node(pointer) }; | |
| slab.free(entry); | |
| // Release the freed object's reference to the slab. | |
| drop(slab); | |
| } | |
| // Returns a reference to a slab with at least one available entry. Allocates | |
| // a new slab if no slabs have available entries. | |
| fn get_slab(&self) -> Result<RefPtr<Slab<T, A>>, Status> { | |
| let _trace = crate::ktrace_rs::begin_scope_cond!( | |
| TRACE_LEVEL >= DETAIL, | |
| "kernel:sched", | |
| "Slab::GetSlab" | |
| ); | |
| ksync::lock!(let mut guard = self.lock_lock()); | |
| let fields = guard.fields(); | |
| if fields.partial_list.is_empty() && fields.empty_list.is_empty() { | |
| return self.allocate_slab_locked(&mut guard.as_mut().fields_mut()); | |
| } | |
| let slab_ref = if fields.partial_list.is_empty() { | |
| fields.empty_list.front().expect("empty_list not empty") | |
| } else { | |
| fields.partial_list.front().expect("partial_list not empty") | |
| }; | |
| Ok(RefPtr::from_ref(slab_ref)) | |
| } | |
| // Allocates a new slab and adds it to the empty list. | |
| fn allocate_slab_locked( | |
| &self, | |
| fields: &mut SingleCacheLockFieldsMut<'_, T, A>, | |
| ) -> Result<RefPtr<Slab<T, A>>, Status> { | |
| let _trace = crate::ktrace_rs::begin_scope_cond!( | |
| TRACE_LEVEL >= DETAIL, | |
| "kernel:sched", | |
| "Slab::AllocateSlab" | |
| ); | |
| let base = self.allocator.allocate()?; | |
| self.allocator.count_slab_allocation(); | |
| let slab = Slab::try_new(NonNull::from(self), base); | |
| fields.empty_list.as_mut().push_front(slab.clone()); | |
| *fields.slab_count += 1; | |
| Ok(slab) | |
| } | |
| // Removes the given slab from this cache. | |
| fn remove_slab_locked(fields: &mut SingleCacheLockFieldsMut<'_, T, A>, slab: &Slab<T, A>) { | |
| let _trace = crate::ktrace_rs::begin_scope_cond!( | |
| TRACE_LEVEL >= DETAIL, | |
| "kernel:sched", | |
| "Slab::RemoveSlab" | |
| ); | |
| // SAFETY: `slab` is in `empty_list` when `remove_slab_locked` is called. | |
| let erased = unsafe { fields.empty_list.as_mut().erase(slab) }; | |
| debug_assert!(erased.is_some()); | |
| *fields.slab_count -= 1; | |
| } | |
| // Predicate indicating whether the number of slabs is greater than the reserve value. | |
| fn should_trim_locked(&self, fields: &SingleCacheLockFieldsMut<'_, T, A>) -> bool { | |
| *fields.slab_count > self.reserve_slabs | |
| } | |
| } | |
| /// Internal implementation of `ObjectCache<T, PerCpu, A>`. | |
| pub struct PerCpuCache<T, A: SlabAllocator = DefaultAllocator> { | |
| processor_count: usize, | |
| cpu_caches: Option<Pin<Array<ObjectCache<T, Single, A>>>>, | |
| } | |
| // SAFETY: Each per-CPU `ObjectCache<T, Single, A>` element is `Send` and `Sync` when `T: Send` and | |
| // `A: SlabAllocator + Send + Sync`. | |
| unsafe impl<T: Send, A: SlabAllocator + Send + Sync> Send for PerCpuCache<T, A> {} | |
| // SAFETY: Shared access to `PerCpuCache` delegates to per-CPU `ObjectCache<T, Single, A>` | |
| // instances which synchronize access via their internal locks and atomics. | |
| unsafe impl<T: Send, A: SlabAllocator + Send + Sync> Sync for PerCpuCache<T, A> {} | |
| impl<T, A: SlabAllocator> Default for PerCpuCache<T, A> { | |
| fn default() -> Self { | |
| Self { processor_count: 0, cpu_caches: None } | |
| } | |
| } | |
| /// A power-of-two slab allocator cache for objects of type `T`. | |
| /// | |
| /// Use `Single` (the default) for a single shared cache or `PerCpu` for per-CPU slab caches. | |
| #[pin_data] | |
| pub struct ObjectCache<T, M: CacheMode<T, A> = Single, A: SlabAllocator = DefaultAllocator> { | |
| #[pin] | |
| inner: M::Inner, | |
| } | |
| impl<T, M: CacheMode<T, A>, A: SlabAllocator> ObjectCache<T, M, A> { | |
| /// Number of objects that fit in a single slab. | |
| pub const OBJECTS_PER_SLAB: usize = Slab::<T, A>::ENTRIES_PER_SLAB; | |
| /// Size of the slab control block in bytes. | |
| pub const SLAB_CONTROL_SIZE: usize = Slab::<T, A>::SLAB_CONTROL_SIZE; | |
| /// Size of a single slab entry in bytes. | |
| pub const ENTRY_SIZE: usize = size_of::<Entry<T>>(); | |
| } | |
| impl<T, A: SlabAllocator> ObjectCache<T, Single, A> { | |
| /// Creates an in-place initializer for a single `ObjectCache` using `A::default()`. | |
| /// | |
| /// Reserve slabs are not immediately allocated. | |
| pub fn new(reserve_slabs: usize) -> impl PinInit<Self, Infallible> | |
| where | |
| A: Default, | |
| { | |
| Self::new_with(reserve_slabs, A::default()) | |
| } | |
| /// Creates an in-place initializer for a single `ObjectCache` with the given `allocator`. | |
| /// | |
| /// Reserve slabs are not immediately allocated. | |
| pub fn new_with(reserve_slabs: usize, allocator: A) -> impl PinInit<Self, Infallible> { | |
| pin_init!(Self { | |
| inner <- SingleCache::new_with(reserve_slabs, allocator), | |
| }) | |
| } | |
| /// Allocates an instance of `T` from a slab and initializes it with `value`. | |
| /// | |
| /// If `T` is reference-counted, it is not yet adopted; use [`UniquePtr::into_ref_ptr`] to | |
| /// adopt it into a `fbl::RefPtr<T>`. | |
| pub fn allocate(&self, value: T) -> Result<UniquePtr<T, A>, Status> { | |
| // SAFETY: The closure writes `value` into `slot` and returns `Ok(())`. | |
| let init = unsafe { | |
| init_from_closure(move |slot: *mut T| -> Result<(), Status> { | |
| slot.write(value); | |
| Ok(()) | |
| }) | |
| }; | |
| self.allocate_init(init) | |
| } | |
| /// Allocates an instance of `T` from a slab and initializes it in place with `init` outside | |
| /// of internal cache locks. | |
| pub fn allocate_init<E>(&self, init: impl Init<T, E>) -> Result<UniquePtr<T, A>, E> | |
| where | |
| E: From<Status>, | |
| { | |
| self.inner.allocate_init(init) | |
| } | |
| /// Returns the number of slabs currently held by this cache. | |
| pub fn slab_count(&self) -> usize { | |
| self.inner.slab_count() | |
| } | |
| /// Returns a shared reference to the underlying slab allocator. | |
| pub fn allocator(&self) -> &A { | |
| &self.inner.allocator | |
| } | |
| /// Returns an already-destroyed object slot to the slab it was allocated from. | |
| /// | |
| /// # Safety | |
| /// | |
| /// `pointer` must point to a slot allocated from an `ObjectCache<T, _, A>` whose `T` value | |
| /// has already been dropped in place and has not yet been returned to its slab. | |
| pub unsafe fn delete_raw(pointer: NonNull<T>) { | |
| // SAFETY: Caller upholds the `delete_raw` safety preconditions. | |
| unsafe { SingleCache::<T, A>::delete_raw(pointer) }; | |
| } | |
| } | |
| impl<T, A: SlabAllocator> Default for ObjectCache<T, PerCpu, A> { | |
| fn default() -> Self { | |
| Self { inner: PerCpuCache::default() } | |
| } | |
| } | |
| impl<T, A: SlabAllocator> ObjectCache<T, PerCpu, A> { | |
| /// Creates a per-CPU `ObjectCache` using `A::default()` for each CPU cache. | |
| /// | |
| /// The `reserve_slabs` value applies to each per-CPU cache independently. Reserve slabs are | |
| /// not immediately allocated. | |
| pub fn try_new(reserve_slabs: usize) -> Result<Self, Status> | |
| where | |
| A: Default, | |
| { | |
| Self::try_new_with(reserve_slabs, A::default) | |
| } | |
| /// Creates a per-CPU `ObjectCache` constructing each CPU's allocator with `factory`. | |
| /// | |
| /// The `reserve_slabs` value applies to each per-CPU cache independently. Reserve slabs are | |
| /// not immediately allocated. | |
| pub fn try_new_with( | |
| reserve_slabs: usize, | |
| mut factory: impl FnMut() -> A, | |
| ) -> Result<Self, Status> { | |
| let processor_count = get_processor_count(); | |
| let cpu_caches = Array::try_new_pin_init(processor_count, || { | |
| ObjectCache::<T, Single, A>::new_with(reserve_slabs, factory()) | |
| }) | |
| .map_err(|_| Status::NO_MEMORY)?; | |
| Ok(Self { inner: PerCpuCache { processor_count, cpu_caches: Some(cpu_caches) } }) | |
| } | |
| /// Allocates an instance of `T` from the current CPU's slab cache and initializes it with | |
| /// `value`. | |
| /// | |
| /// If `T` is reference-counted, it is not yet adopted; use [`UniquePtr::into_ref_ptr`] to | |
| /// adopt it into a `fbl::RefPtr<T>`. | |
| pub fn allocate(&self, value: T) -> Result<UniquePtr<T, A>, Status> { | |
| // SAFETY: The closure writes `value` into `slot` and returns `Ok(())`. | |
| let init = unsafe { | |
| init_from_closure(move |slot: *mut T| -> Result<(), Status> { | |
| slot.write(value); | |
| Ok(()) | |
| }) | |
| }; | |
| self.allocate_init(init) | |
| } | |
| /// Allocates an instance of `T` from the current CPU's slab cache and initializes it in place | |
| /// with `init` outside of internal cache locks. | |
| pub fn allocate_init<E>(&self, init: impl Init<T, E>) -> Result<UniquePtr<T, A>, E> | |
| where | |
| E: From<Status>, | |
| { | |
| debug_assert!(self.inner.cpu_caches.is_some()); | |
| debug_assert!(current_memory_allocation_state_is_enabled()); | |
| let _preempt_disable = AutoPreemptDisabler::new(); | |
| let current_cpu = curr_cpu_num() as usize; | |
| debug_assert!(current_cpu < self.inner.processor_count); | |
| let cpu_caches = self.inner.cpu_caches.as_ref().expect("cpu_caches initialized"); | |
| cpu_caches[current_cpu].allocate_init(init) | |
| } | |
| /// Returns the total number of slabs currently held across all per-CPU caches. | |
| pub fn slab_count(&self) -> usize { | |
| let mut count = 0; | |
| for i in 0..self.inner.processor_count { | |
| count += self.inner.cpu_caches.as_ref().unwrap()[i].slab_count(); | |
| } | |
| count | |
| } | |
| } | |
| /// object_cache tests | |
| #[cfg(ktest)] | |
| #[unittest::suite(name = "object_cache")] | |
| mod tests { | |
| use crate::kernel::event::{AutounsignalEvent, Event}; | |
| use crate::kernel::thread::{self, ThreadPtr}; | |
| use crate::kernel::types::{cpu_mask_t, cpu_num_to_mask}; | |
| use crate::platform_rs::timer::InstantMono; | |
| use core::ffi::c_void; | |
| use core::sync::atomic::{AtomicI32, AtomicPtr}; | |
| use fbl::Vector; | |
| use pin_init::stack_pin_init; | |
| use unittest::{assert_eq, assert_true, expect_eq, expect_false, expect_true}; | |
| static GLOBAL_ALLOCATED_SLABS: AtomicI32 = AtomicI32::new(0); | |
| static GLOBAL_FREED_SLABS: AtomicI32 = AtomicI32::new(0); | |
| #[derive(Default, Clone, Copy)] | |
| struct TestGlobalAllocator; | |
| impl TestGlobalAllocator { | |
| // Ignores the allocator arguments passed to the ObjectCache ctor. | |
| const fn new(_a: i32, _b: i32) -> Self { | |
| Self | |
| } | |
| fn reset_counts() { | |
| GLOBAL_ALLOCATED_SLABS.store(0, Ordering::SeqCst); | |
| GLOBAL_FREED_SLABS.store(0, Ordering::SeqCst); | |
| } | |
| fn allocated_slabs() -> i32 { | |
| GLOBAL_ALLOCATED_SLABS.load(Ordering::SeqCst) | |
| } | |
| fn freed_slabs() -> i32 { | |
| GLOBAL_FREED_SLABS.load(Ordering::SeqCst) | |
| } | |
| } | |
| // SAFETY: `TestGlobalAllocator` delegates `page::SIZE` aligned allocation and global release | |
| // to `DefaultAllocator`, allowing orphan slabs to outlive the cache. | |
| unsafe impl GlobalSlabAllocator for TestGlobalAllocator { | |
| const SLAB_SIZE: usize = DefaultAllocator::SLAB_SIZE; | |
| fn allocate() -> Result<NonNull<u8>, Status> { | |
| GLOBAL_ALLOCATED_SLABS.fetch_add(1, Ordering::Relaxed); | |
| DefaultAllocator::allocate() | |
| } | |
| /// # Safety | |
| /// | |
| /// `slab` must be the base address of a slab allocated from `DefaultAllocator`. | |
| unsafe fn release(slab: NonNull<u8>) { | |
| GLOBAL_FREED_SLABS.fetch_add(1, Ordering::Relaxed); | |
| // SAFETY: Caller guarantees `slab` came from `DefaultAllocator::allocate`. | |
| unsafe { DefaultAllocator::release(slab) }; | |
| } | |
| fn count_object_allocation() { | |
| DefaultAllocator::count_object_allocation(); | |
| } | |
| fn count_object_free() { | |
| DefaultAllocator::count_object_free(); | |
| } | |
| fn count_slab_allocation() { | |
| DefaultAllocator::count_slab_allocation(); | |
| } | |
| fn count_slab_free() { | |
| DefaultAllocator::count_slab_free(); | |
| } | |
| } | |
| static LOCAL_TOTAL_ALLOCATED_SLABS: AtomicI32 = AtomicI32::new(0); | |
| static LOCAL_TOTAL_FREED_SLABS: AtomicI32 = AtomicI32::new(0); | |
| struct TestLocalAllocator { | |
| local_state_a: i32, | |
| local_state_b: i32, | |
| local_allocated_slabs: AtomicI32, | |
| local_freed_slabs: AtomicI32, | |
| } | |
| impl TestLocalAllocator { | |
| // Stores the allocator arguments passed to the ObjectCache ctor for verification. | |
| const fn new(a: i32, b: i32) -> Self { | |
| Self { | |
| local_state_a: a, | |
| local_state_b: b, | |
| local_allocated_slabs: AtomicI32::new(0), | |
| local_freed_slabs: AtomicI32::new(0), | |
| } | |
| } | |
| fn reset_counts() { | |
| LOCAL_TOTAL_ALLOCATED_SLABS.store(0, Ordering::SeqCst); | |
| LOCAL_TOTAL_FREED_SLABS.store(0, Ordering::SeqCst); | |
| } | |
| fn allocated_slabs() -> i32 { | |
| LOCAL_TOTAL_ALLOCATED_SLABS.load(Ordering::SeqCst) | |
| } | |
| fn freed_slabs() -> i32 { | |
| LOCAL_TOTAL_FREED_SLABS.load(Ordering::SeqCst) | |
| } | |
| } | |
| // SAFETY: `TestLocalAllocator` delegates `page::SIZE` aligned allocation to `DefaultAllocator` | |
| // and sets `IS_STATEFUL = true` because it tracks per-instance slab counters. | |
| unsafe impl SlabAllocator for TestLocalAllocator { | |
| const SLAB_SIZE: usize = DefaultAllocator::SLAB_SIZE; | |
| const IS_STATEFUL: bool = true; | |
| fn allocate(&self) -> Result<NonNull<u8>, Status> { | |
| LOCAL_TOTAL_ALLOCATED_SLABS.fetch_add(1, Ordering::Relaxed); | |
| self.local_allocated_slabs.fetch_add(1, Ordering::Relaxed); | |
| DefaultAllocator::allocate() | |
| } | |
| /// # Safety | |
| /// | |
| /// `slab` must be the base address of a slab allocated from `DefaultAllocator`. | |
| unsafe fn release(&self, slab: NonNull<u8>) { | |
| LOCAL_TOTAL_FREED_SLABS.fetch_add(1, Ordering::Relaxed); | |
| self.local_freed_slabs.fetch_add(1, Ordering::Relaxed); | |
| // SAFETY: Caller guarantees `slab` came from `DefaultAllocator::allocate`. | |
| unsafe { DefaultAllocator::release(slab) }; | |
| } | |
| fn count_object_allocation(&self) { | |
| DefaultAllocator::count_object_allocation(); | |
| } | |
| fn count_object_free(&self) { | |
| DefaultAllocator::count_object_free(); | |
| } | |
| fn count_slab_allocation(&self) { | |
| DefaultAllocator::count_slab_allocation(); | |
| } | |
| fn count_slab_free(&self) { | |
| DefaultAllocator::count_slab_free(); | |
| } | |
| } | |
| static TEST_OBJECT_CONSTRUCTOR_COUNT: AtomicI32 = AtomicI32::new(0); | |
| static TEST_OBJECT_DESTRUCTOR_COUNT: AtomicI32 = AtomicI32::new(0); | |
| #[repr(C)] | |
| struct TestObject { | |
| data: i32, | |
| _extra: [u8; 256 - size_of::<i32>()], | |
| } | |
| zr::static_assert!(size_of::<TestObject>() == 256); | |
| impl TestObject { | |
| fn new(data: i32) -> Self { | |
| TEST_OBJECT_CONSTRUCTOR_COUNT.fetch_add(1, Ordering::SeqCst); | |
| Self { data, _extra: [0; 256 - size_of::<i32>()] } | |
| } | |
| fn reset_counts() { | |
| TEST_OBJECT_CONSTRUCTOR_COUNT.store(0, Ordering::SeqCst); | |
| TEST_OBJECT_DESTRUCTOR_COUNT.store(0, Ordering::SeqCst); | |
| } | |
| fn constructor_count() -> i32 { | |
| TEST_OBJECT_CONSTRUCTOR_COUNT.load(Ordering::SeqCst) | |
| } | |
| fn destructor_count() -> i32 { | |
| TEST_OBJECT_DESTRUCTOR_COUNT.load(Ordering::SeqCst) | |
| } | |
| } | |
| impl Drop for TestObject { | |
| fn drop(&mut self) { | |
| TEST_OBJECT_DESTRUCTOR_COUNT.fetch_add(1, Ordering::SeqCst); | |
| } | |
| } | |
| static TEST_PARENT_DESTRUCTOR_COUNT: AtomicI32 = AtomicI32::new(0); | |
| #[fbl::ref_counted] | |
| #[pin_data(PinnedDrop)] | |
| #[derive(fbl::Recyclable)] | |
| #[repr(C)] | |
| struct TestParent { | |
| #[pin] | |
| allocator: ObjectCache<Child, Single, TestGlobalAllocator>, | |
| } | |
| #[pinned_drop] | |
| impl PinnedDrop for TestParent { | |
| fn drop(self: Pin<&mut Self>) { | |
| TEST_PARENT_DESTRUCTOR_COUNT.fetch_add(1, Ordering::SeqCst); | |
| } | |
| } | |
| #[fbl::ref_counted] | |
| #[repr(C)] | |
| struct Child { | |
| parent: RefPtr<TestParent>, | |
| } | |
| impl_object_cache_deletable!(Child, TestGlobalAllocator); | |
| impl Child { | |
| fn new(parent: RefPtr<TestParent>) -> Self { | |
| Self { ref_count: RefCounted::new(), __fbl_ref_counted_guard: (), parent } | |
| } | |
| } | |
| impl TestParent { | |
| const OBJECTS_PER_SLAB: usize = | |
| ObjectCache::<Child, Single, TestGlobalAllocator>::OBJECTS_PER_SLAB; | |
| const RESERVE_SLABS: usize = 1; | |
| fn try_new() -> Result<RefPtr<Self>, Status> { | |
| fbl::pin_make_ref_counted!(TestParent { | |
| allocator <- ObjectCache::new(Self::RESERVE_SLABS), | |
| }) | |
| .map_err(|_| Status::NO_MEMORY) | |
| } | |
| fn allocate(this: &RefPtr<Self>) -> Result<RefPtr<Child>, Status> { | |
| let child = this.allocator.allocate(Child::new(this.clone()))?; | |
| Ok(UniquePtr::into_ref_ptr(child)) | |
| } | |
| fn reset_counts() { | |
| TEST_PARENT_DESTRUCTOR_COUNT.store(0, Ordering::SeqCst); | |
| } | |
| fn destructor_count() -> i32 { | |
| TEST_PARENT_DESTRUCTOR_COUNT.load(Ordering::SeqCst) | |
| } | |
| } | |
| struct AffinityGuard { | |
| thread: ThreadPtr, | |
| original_mask: cpu_mask_t, | |
| } | |
| impl AffinityGuard { | |
| // Stay on one CPU during the tests to verify numeric properties of a single per-CPU | |
| // cache. Accounting for CPU migration during the tests would make them overly complicated | |
| // for little value. | |
| fn pin_to_current_cpu() -> Self { | |
| // SAFETY: Tests run after threading is initialized on a live kernel thread. | |
| let thread = unsafe { ThreadPtr::current() }; | |
| // SAFETY: `thread` is the live current thread. | |
| let original_mask = unsafe { thread.get_cpu_affinity() }; | |
| { | |
| let _preempt_disable = AutoPreemptDisabler::new(); | |
| let current_cpu = curr_cpu_num(); | |
| // SAFETY: `thread` is the live current thread. | |
| unsafe { | |
| thread.set_cpu_affinity(cpu_num_to_mask(current_cpu)); | |
| } | |
| } | |
| Self { thread, original_mask } | |
| } | |
| } | |
| impl Drop for AffinityGuard { | |
| fn drop(&mut self) { | |
| // SAFETY: `self.thread` is the live current thread. | |
| unsafe { | |
| self.thread.set_cpu_affinity(self.original_mask); | |
| } | |
| } | |
| } | |
| trait TestAllocatorCounts: SlabAllocator { | |
| fn reset_counts(); | |
| fn total_allocated_slabs() -> i32; | |
| fn total_freed_slabs() -> i32; | |
| fn make(a: i32, b: i32) -> Self; | |
| fn check_local_args(&self, _a: i32, _b: i32) -> bool { | |
| true | |
| } | |
| fn check_local_counts(&self, _allocated: i32, _freed: i32, _slab_count: usize) -> bool { | |
| true | |
| } | |
| } | |
| impl TestAllocatorCounts for TestGlobalAllocator { | |
| fn reset_counts() { | |
| Self::reset_counts(); | |
| } | |
| fn total_allocated_slabs() -> i32 { | |
| Self::allocated_slabs() | |
| } | |
| fn total_freed_slabs() -> i32 { | |
| Self::freed_slabs() | |
| } | |
| fn make(a: i32, b: i32) -> Self { | |
| Self::new(a, b) | |
| } | |
| } | |
| impl TestAllocatorCounts for TestLocalAllocator { | |
| fn reset_counts() { | |
| Self::reset_counts(); | |
| } | |
| fn total_allocated_slabs() -> i32 { | |
| Self::allocated_slabs() | |
| } | |
| fn total_freed_slabs() -> i32 { | |
| Self::freed_slabs() | |
| } | |
| fn make(a: i32, b: i32) -> Self { | |
| Self::new(a, b) | |
| } | |
| fn check_local_args(&self, a: i32, b: i32) -> bool { | |
| (unittest::subtest!(|this: &Self, a: i32, b: i32| { | |
| expect_eq!(a, this.local_state_a); | |
| expect_eq!(b, this.local_state_b); | |
| }))(self, a, b) | |
| } | |
| fn check_local_counts(&self, allocated: i32, freed: i32, slab_count: usize) -> bool { | |
| (unittest::subtest!(|this: &Self, allocated: i32, freed: i32, slab_count: usize| { | |
| let local_alloc = this.local_allocated_slabs.load(Ordering::SeqCst); | |
| let local_free = this.local_freed_slabs.load(Ordering::SeqCst); | |
| expect_eq!(allocated, local_alloc); | |
| expect_eq!(freed, local_free); | |
| expect_eq!(local_alloc - local_free, slab_count as i32); | |
| }))(self, allocated, freed, slab_count) | |
| } | |
| } | |
| trait TestCache<A: TestAllocatorCounts> { | |
| fn allocate(&self, value: TestObject) -> Result<UniquePtr<TestObject, A>, Status>; | |
| fn slab_count(&self) -> usize; | |
| fn check_local_args(&self, _a: i32, _b: i32) -> bool { | |
| true | |
| } | |
| fn check_local_counts(&self, _allocated: i32, _freed: i32, _slab_count: usize) -> bool { | |
| true | |
| } | |
| } | |
| impl<A: TestAllocatorCounts> TestCache<A> for Pin<Array<ObjectCache<TestObject, Single, A>>> { | |
| fn allocate(&self, value: TestObject) -> Result<UniquePtr<TestObject, A>, Status> { | |
| self[0].allocate(value) | |
| } | |
| fn slab_count(&self) -> usize { | |
| self[0].slab_count() | |
| } | |
| fn check_local_args(&self, a: i32, b: i32) -> bool { | |
| self[0].allocator().check_local_args(a, b) | |
| } | |
| fn check_local_counts(&self, allocated: i32, freed: i32, slab_count: usize) -> bool { | |
| self[0].allocator().check_local_counts(allocated, freed, slab_count) | |
| } | |
| } | |
| impl<A: TestAllocatorCounts> TestCache<A> for ObjectCache<TestObject, PerCpu, A> { | |
| fn allocate(&self, value: TestObject) -> Result<UniquePtr<TestObject, A>, Status> { | |
| ObjectCache::<TestObject, PerCpu, A>::allocate(self, value) | |
| } | |
| fn slab_count(&self) -> usize { | |
| ObjectCache::<TestObject, PerCpu, A>::slab_count(self) | |
| } | |
| } | |
| trait TestCacheMode<A: TestAllocatorCounts>: Sized { | |
| type Cache: TestCache<A>; | |
| fn create_cache(retain_slabs: usize) -> Option<Self::Cache>; | |
| } | |
| impl<A: TestAllocatorCounts> TestCacheMode<A> for Single { | |
| type Cache = Pin<Array<ObjectCache<TestObject, Single, A>>>; | |
| fn create_cache(retain_slabs: usize) -> Option<Self::Cache> { | |
| // Heap-pin in a 1-element `Array` since a pinned `ObjectCache<_, Single, _>` cannot be | |
| // returned by value. | |
| Array::try_new_pin_init(1, || { | |
| ObjectCache::<TestObject, Single, A>::new_with(retain_slabs, A::make(10, 20)) | |
| }) | |
| .ok() | |
| } | |
| } | |
| impl<A: TestAllocatorCounts> TestCacheMode<A> for PerCpu { | |
| type Cache = ObjectCache<TestObject, PerCpu, A>; | |
| fn create_cache(retain_slabs: usize) -> Option<Self::Cache> { | |
| ObjectCache::<TestObject, PerCpu, A>::try_new_with(retain_slabs, || A::make(10, 20)) | |
| .ok() | |
| } | |
| } | |
| fn object_cache_tests< | |
| const RETAIN_SLABS: usize, | |
| const SLAB_COUNT: usize, | |
| M: TestCacheMode<A>, | |
| A: TestAllocatorCounts, | |
| >() -> bool { | |
| (unittest::subtest!(|retain_slabs: usize, slab_count: usize| { | |
| TestObject::reset_counts(); | |
| assert_eq!(0, TestObject::constructor_count()); | |
| assert_eq!(0, TestObject::destructor_count()); | |
| A::reset_counts(); | |
| assert_eq!(0, A::total_allocated_slabs()); | |
| assert_eq!(0, A::total_freed_slabs()); | |
| let objects_per_slab = ObjectCache::<TestObject>::OBJECTS_PER_SLAB; | |
| let object_count = objects_per_slab * slab_count; | |
| let mut objects = Vector::<Option<UniquePtr<TestObject, A>>>::new(); | |
| assert_true!(objects.reserve(object_count).is_ok()); | |
| { | |
| let object_cache = M::create_cache(retain_slabs); | |
| assert_true!(object_cache.is_some()); | |
| let object_cache = object_cache.unwrap(); | |
| expect_true!(object_cache.check_local_args(10, 20)); | |
| // Stay on one CPU during the following tests to verify numeric properties of a | |
| // single per-CPU cache. Accounting for CPU migration during the tests would make | |
| // them overly complicated for little value. | |
| let _affinity = AffinityGuard::pin_to_current_cpu(); | |
| expect_eq!(0usize, object_cache.slab_count()); | |
| expect_eq!(0, TestObject::constructor_count()); | |
| expect_eq!(0, TestObject::destructor_count()); | |
| for i in 0..object_count { | |
| let result = object_cache.allocate(TestObject::new(i as i32)); | |
| assert_true!(result.is_ok()); | |
| let ptr = result.unwrap(); | |
| expect_eq!((i + 1) as i32, TestObject::constructor_count()); | |
| expect_eq!(0, TestObject::destructor_count()); | |
| expect_eq!(i as i32, ptr.data); | |
| assert_true!(objects.push_back(Some(ptr)).is_ok()); | |
| } | |
| expect_eq!(slab_count, object_cache.slab_count()); | |
| expect_eq!(slab_count as i32, A::total_allocated_slabs()); | |
| expect_eq!(0, A::total_freed_slabs()); | |
| expect_true!(object_cache.check_local_counts( | |
| slab_count as i32, | |
| 0, | |
| object_cache.slab_count(), | |
| )); | |
| // Release the first slab worth of objects. | |
| for i in 0..objects_per_slab { | |
| objects[i] = None; | |
| expect_eq!(object_count as i32, TestObject::constructor_count()); | |
| expect_eq!((i + 1) as i32, TestObject::destructor_count()); | |
| } | |
| let expected_freed = if slab_count <= retain_slabs { 0 } else { 1 }; | |
| expect_eq!(slab_count as i32, A::total_allocated_slabs()); | |
| expect_eq!(expected_freed, A::total_freed_slabs()); | |
| expect_eq!( | |
| A::total_allocated_slabs() - A::total_freed_slabs(), | |
| object_cache.slab_count() as i32 | |
| ); | |
| expect_true!(object_cache.check_local_counts( | |
| slab_count as i32, | |
| expected_freed, | |
| object_cache.slab_count(), | |
| )); | |
| objects.clear(); | |
| expect_eq!(TestObject::constructor_count(), TestObject::destructor_count()); | |
| expect_eq!(object_count as i32, TestObject::destructor_count()); | |
| } | |
| expect_eq!(TestObject::constructor_count(), TestObject::destructor_count()); | |
| expect_eq!(A::total_allocated_slabs(), A::total_freed_slabs()); | |
| }))(RETAIN_SLABS, SLAB_COUNT) | |
| } | |
| /// object_cache_tests<0, 2, Single, TestLocalAllocator> | |
| #[test] | |
| fn object_cache_single_0_2_local() { | |
| assert_true!(object_cache_tests::<0, 2, Single, TestLocalAllocator>()); | |
| } | |
| /// object_cache_tests<1, 2, Single, TestLocalAllocator> | |
| #[test] | |
| fn object_cache_single_1_2_local() { | |
| assert_true!(object_cache_tests::<1, 2, Single, TestLocalAllocator>()); | |
| } | |
| /// object_cache_tests<2, 2, Single, TestLocalAllocator> | |
| #[test] | |
| fn object_cache_single_2_2_local() { | |
| assert_true!(object_cache_tests::<2, 2, Single, TestLocalAllocator>()); | |
| } | |
| /// object_cache_tests<0, 2, PerCpu, TestLocalAllocator> | |
| #[test] | |
| fn object_cache_per_cpu_0_2_local() { | |
| assert_true!(object_cache_tests::<0, 2, PerCpu, TestLocalAllocator>()); | |
| } | |
| /// object_cache_tests<1, 2, PerCpu, TestLocalAllocator> | |
| #[test] | |
| fn object_cache_per_cpu_1_2_local() { | |
| assert_true!(object_cache_tests::<1, 2, PerCpu, TestLocalAllocator>()); | |
| } | |
| /// object_cache_tests<2, 2, PerCpu, TestLocalAllocator> | |
| #[test] | |
| fn object_cache_per_cpu_2_2_local() { | |
| assert_true!(object_cache_tests::<2, 2, PerCpu, TestLocalAllocator>()); | |
| } | |
| /// object_cache_tests<0, 2, Single, TestGlobalAllocator> | |
| #[test] | |
| fn object_cache_single_0_2_global() { | |
| assert_true!(object_cache_tests::<0, 2, Single, TestGlobalAllocator>()); | |
| } | |
| /// object_cache_tests<1, 2, Single, TestGlobalAllocator> | |
| #[test] | |
| fn object_cache_single_1_2_global() { | |
| assert_true!(object_cache_tests::<1, 2, Single, TestGlobalAllocator>()); | |
| } | |
| /// object_cache_tests<2, 2, Single, TestGlobalAllocator> | |
| #[test] | |
| fn object_cache_single_2_2_global() { | |
| assert_true!(object_cache_tests::<2, 2, Single, TestGlobalAllocator>()); | |
| } | |
| /// object_cache_tests<0, 2, PerCpu, TestGlobalAllocator> | |
| #[test] | |
| fn object_cache_per_cpu_0_2_global() { | |
| assert_true!(object_cache_tests::<0, 2, PerCpu, TestGlobalAllocator>()); | |
| } | |
| /// object_cache_tests<1, 2, PerCpu, TestGlobalAllocator> | |
| #[test] | |
| fn object_cache_per_cpu_1_2_global() { | |
| assert_true!(object_cache_tests::<1, 2, PerCpu, TestGlobalAllocator>()); | |
| } | |
| /// object_cache_tests<2, 2, PerCpu, TestGlobalAllocator> | |
| #[test] | |
| fn object_cache_per_cpu_2_2_global() { | |
| assert_true!(object_cache_tests::<2, 2, PerCpu, TestGlobalAllocator>()); | |
| } | |
| /// backreference_lifetime_tests | |
| #[test] | |
| fn backreference_lifetime_tests() { | |
| TestParent::reset_counts(); | |
| expect_eq!(0, TestParent::destructor_count()); | |
| TestGlobalAllocator::reset_counts(); | |
| assert_eq!(0, TestGlobalAllocator::allocated_slabs()); | |
| assert_eq!(0, TestGlobalAllocator::freed_slabs()); | |
| let parent = TestParent::try_new(); | |
| assert_true!(parent.is_ok()); | |
| let parent = parent.unwrap(); | |
| let result1 = TestParent::allocate(&parent); | |
| let result2 = TestParent::allocate(&parent); | |
| let result3 = TestParent::allocate(&parent); | |
| assert_true!(result1.is_ok()); | |
| assert_true!(result2.is_ok()); | |
| assert_true!(result3.is_ok()); | |
| drop(parent); | |
| expect_eq!(0, TestParent::destructor_count()); | |
| drop(result1); | |
| expect_eq!(0, TestParent::destructor_count()); | |
| drop(result2); | |
| expect_eq!(0, TestParent::destructor_count()); | |
| drop(result3); | |
| expect_eq!(1, TestParent::destructor_count()); | |
| expect_eq!(TestGlobalAllocator::allocated_slabs(), TestGlobalAllocator::freed_slabs()); | |
| } | |
| #[pin_data] | |
| struct StressControl { | |
| parent: AtomicPtr<TestParent>, | |
| #[pin] | |
| allocation_event: AutounsignalEvent, | |
| #[pin] | |
| deallocation_event: Event, | |
| #[pin] | |
| finished_event: AutounsignalEvent, | |
| failed: AtomicBool, | |
| count: AtomicI32, | |
| } | |
| extern "C" fn stress_worker_entry(arg: *mut c_void) -> i32 { | |
| // SAFETY: `arg` points to a `StressControl` pinned on the caller's stack that outlives all | |
| // worker threads (which are joined before `StressControl` goes out of scope). | |
| let control = unsafe { &*(arg as *const StressControl) }; | |
| let object_count = TestParent::OBJECTS_PER_SLAB; | |
| let mut objects = Vector::<Option<RefPtr<Child>>>::new(); | |
| if objects.reserve(object_count).is_err() { | |
| control.failed.store(true, Ordering::SeqCst); | |
| return Status::NO_MEMORY.into_raw(); | |
| } | |
| for _ in 0..object_count { | |
| let parent_ptr = control.parent.load(Ordering::SeqCst); | |
| // SAFETY: `parent_ptr` is non-null and kept alive by the main thread during the | |
| // allocation phase. | |
| let parent_ref = RefPtr::from_ref(unsafe { &*parent_ptr }); | |
| let child = match TestParent::allocate(&parent_ref) { | |
| Ok(child) => child, | |
| Err(status) => { | |
| control.failed.store(true, Ordering::SeqCst); | |
| return status.into_raw(); | |
| } | |
| }; | |
| if objects.push_back(Some(child)).is_err() { | |
| control.failed.store(true, Ordering::SeqCst); | |
| return Status::NO_MEMORY.into_raw(); | |
| } | |
| control.count.fetch_add(1, Ordering::SeqCst); | |
| thread::r#yield(); | |
| } | |
| control.allocation_event.signal(); | |
| let _ = control.deallocation_event.wait_infinite(); | |
| for i in 0..object_count { | |
| objects[i] = None; | |
| control.count.fetch_sub(1, Ordering::SeqCst); | |
| thread::r#yield(); | |
| } | |
| control.finished_event.signal(); | |
| zx_status::sys::ZX_OK | |
| } | |
| /// backreference_lifetime_stress_tests | |
| #[test] | |
| fn backreference_lifetime_stress_tests() { | |
| const STRESS_TEST_ITERATIONS: usize = 1000; | |
| const THREAD_COUNT: usize = 8; | |
| for _ in 0..STRESS_TEST_ITERATIONS { | |
| TestParent::reset_counts(); | |
| expect_eq!(0, TestParent::destructor_count()); | |
| TestGlobalAllocator::reset_counts(); | |
| assert_eq!(0, TestGlobalAllocator::allocated_slabs()); | |
| assert_eq!(0, TestGlobalAllocator::freed_slabs()); | |
| let parent = TestParent::try_new(); | |
| assert_true!(parent.is_ok()); | |
| let parent = parent.unwrap(); | |
| let object_count = TestParent::OBJECTS_PER_SLAB; | |
| let parent_raw = RefPtr::as_ptr(&parent).cast_mut(); | |
| stack_pin_init!(let control = pin_init!(StressControl { | |
| parent: AtomicPtr::new(parent_raw), | |
| allocation_event <- AutounsignalEvent::init_unsignaled(), | |
| deallocation_event <- Event::init_unsignaled(), | |
| finished_event <- AutounsignalEvent::init_unsignaled(), | |
| failed: AtomicBool::new(false), | |
| count: AtomicI32::new(0), | |
| })); | |
| let control_ptr = (&raw const *control).cast_mut().cast::<c_void>(); | |
| let mut threads: [Option<ThreadPtr>; THREAD_COUNT] = [None; THREAD_COUNT]; | |
| for slot in &mut threads { | |
| // SAFETY: `stress_worker_entry` only accesses `control`, which outlives every | |
| // worker thread because we join all threads before `control` drops. | |
| let created = unsafe { | |
| thread::create(c"ObjectCacheRace".as_ptr(), stress_worker_entry, control_ptr) | |
| }; | |
| assert_true!(created.is_ok()); | |
| *slot = created.ok(); | |
| } | |
| // Resume threads in quick succession to get maximum overlap in the allocation | |
| // phase. | |
| for slot in &threads { | |
| let thread = slot.expect("thread created"); | |
| // SAFETY: `thread` was just created and has not been resumed or joined yet. | |
| unsafe { | |
| thread.resume(); | |
| } | |
| } | |
| // Wait for each worker to finish allocating children. | |
| let target_count = (THREAD_COUNT * object_count) as i32; | |
| while control.count.load(Ordering::SeqCst) != target_count | |
| && !control.failed.load(Ordering::SeqCst) | |
| { | |
| let _ = control.allocation_event.wait_infinite(); | |
| } | |
| expect_false!(control.failed.load(Ordering::SeqCst)); | |
| expect_eq!(THREAD_COUNT as i32, TestGlobalAllocator::allocated_slabs()); | |
| expect_eq!(0, TestGlobalAllocator::freed_slabs()); | |
| // Workers should not touch the parent object after they finish allocating | |
| // children. | |
| control.parent.store(core::ptr::null_mut(), Ordering::SeqCst); | |
| expect_eq!(0, TestParent::destructor_count()); | |
| // Children should maintain the lifetime of the parent. | |
| drop(parent); | |
| expect_eq!(0, TestParent::destructor_count()); | |
| control.deallocation_event.signal(); | |
| // Wait for each worker to finish freeing children. | |
| while control.count.load(Ordering::SeqCst) != 0 | |
| && !control.failed.load(Ordering::SeqCst) | |
| { | |
| let _ = control.finished_event.wait_infinite(); | |
| } | |
| expect_false!(control.failed.load(Ordering::SeqCst)); | |
| expect_eq!(1, TestParent::destructor_count()); | |
| expect_eq!(THREAD_COUNT as i32, TestGlobalAllocator::allocated_slabs()); | |
| expect_eq!(THREAD_COUNT as i32, TestGlobalAllocator::freed_slabs()); | |
| for slot in threads { | |
| let thread = slot.expect("thread created"); | |
| // SAFETY: Each thread was resumed once and has not been joined yet. | |
| let retcode = unsafe { thread.join(InstantMono::INFINITE) }; | |
| assert_true!(retcode.is_ok()); | |
| expect_eq!(zx_status::sys::ZX_OK, retcode.unwrap()); | |
| } | |
| } | |
| } | |
| } |