diff --git a/README.md b/README.md index 6839182..b9112b2 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,4 @@ A set of memory-management tools for real-time audio and other latency-critical ## License -basedrop is distributed under the terms of both the [MIT license](LICENSE-MIT) and the [Apache license, version 2.0](LICENSE-APACHE). Contributions are accepted under the same terms. +`basedrop` is distributed under the terms of both the [MIT license](LICENSE-MIT) and the [Apache license, version 2.0](LICENSE-APACHE). Contributions are accepted under the same terms. diff --git a/src/collector.rs b/src/collector.rs index b1c4d57..c8b5b68 100644 --- a/src/collector.rs +++ b/src/collector.rs @@ -9,9 +9,20 @@ union NodeLink { next: ManuallyDrop>>, } +/// An allocation that can be added to its associated [`Collector`]'s drop +/// queue. +/// +/// `Node` provides a low-level interface intended for use in the +/// implementation of smart pointers and data structure internals. It is used +/// in the implementations of [`Owned`] and [`Shared`]. +/// +/// [`Collector`]: crate::Collector +/// [`Owned`]: crate::Owned +/// [`Shared`]: crate::Shared pub struct Node { link: NodeLink, drop: unsafe fn(*mut Node<()>), + /// The data stored in this allocation. pub data: T, } @@ -20,6 +31,19 @@ unsafe fn drop_node(node: *mut Node<()>) { } impl Node { + /// Allocates a `Node` with the given data. Note that the `Node` will not + /// be added to the drop queue or freed unless [`queue_drop`] is called. + /// + /// # Examples + /// ``` + /// use basedrop::{Collector, Handle, Node}; + /// + /// let mut collector = Collector::new(); + /// let handle = collector.handle(); + /// let node = Node::alloc(&handle, 3); + /// ``` + /// + /// [`queue_drop`]: crate::Node::queue_drop pub fn alloc(handle: &Handle, data: T) -> *mut Node { unsafe { (*handle.collector).allocs.fetch_add(1, Ordering::Relaxed); @@ -36,6 +60,31 @@ impl Node { } impl Node { + /// Adds a `Node` to its associated [`Collector`]'s drop queue. The `Node` + /// and its contained data may be dropped at a later time when + /// [`Collector::collect`] or [`Collector::collect_one`] is called. + /// + /// The argument must point to a valid `Node` previously allocated with + /// [`Node::alloc`]. `queue_drop` may only be called once for a given + /// `Node`, and the `Node`'s data must not be accessed afterwards. + /// + /// # Examples + /// ``` + /// use basedrop::{Collector, Handle, Node}; + /// + /// let mut collector = Collector::new(); + /// let handle = collector.handle(); + /// let node = Node::alloc(&handle, 3); + /// + /// unsafe { + /// Node::queue_drop(node); + /// } + /// ``` + /// + /// [`Collector`]: crate::Collector + /// [`Collector::collect`]: crate::Collector::collect + /// [`Collector::collect_one`]: crate::Collector::collect_one + /// [`Node::alloc`]: crate::Node::alloc pub unsafe fn queue_drop(node: *mut Node) { let collector = (*node).link.collector; (*node).link.next = ManuallyDrop::new(AtomicPtr::new(core::ptr::null_mut())); @@ -43,6 +92,15 @@ impl Node { (*tail).link.next.store(node as *mut Node<()>, Ordering::Relaxed); } + /// Gets a [`Handle`] to this `Node`'s associated [`Collector`]. + /// + /// The argument must point to a valid `Node` previously allocated with + /// [`Node::alloc`], on which [`queue_drop`] has not been called. + /// + /// [`Handle`]: crate::Collector + /// [`Collector`]: crate::Collector + /// [`Node::alloc`]: crate::Node::alloc + /// [`queue_drop`]: crate::Node::queue_drop pub unsafe fn handle(node: *mut Node) -> Handle { let collector = (*node).link.collector; (*collector).handles.fetch_add(1, Ordering::Relaxed); @@ -50,6 +108,15 @@ impl Node { } } +/// A handle to a [`Collector`], used when allocating [`Owned`] and [`Shared`] +/// values. +/// +/// Multiple `Handle`s to a given [`Collector`] can exist at one time, and they +/// can be safely moved and shared between threads. +/// +/// [`Collector`]: crate::Collector +/// [`Owned`]: crate::Owned +/// [`Shared`]: crate::Shared pub struct Handle { collector: *mut CollectorInner, } @@ -81,6 +148,16 @@ struct CollectorInner { tail: AtomicPtr>, } +/// A garbage collector for [`Owned`] and [`Shared`] allocations. +/// +/// If a `Collector` is dropped, it will leak all associated allocations as +/// well as its internal data structures. To avoid this, ensure that all +/// allocations have been collected and all [`Handle`]s have been dropped, then +/// call [`try_cleanup`]. +/// +/// [`Owned`]: crate::Owned +/// [`Shared`]: crate::Shared +/// [`try_cleanup`]: crate::Collector::try_cleanup pub struct Collector { head: *mut Node<()>, stub: *mut Node<()>, @@ -90,6 +167,7 @@ pub struct Collector { unsafe impl Send for Collector {} impl Collector { + /// Constructs a new `Collector`. pub fn new() -> Collector { let head = Box::into_raw(Box::new(Node { link: NodeLink { @@ -112,6 +190,9 @@ impl Collector { } } + /// Gets a [`Handle`] to this `Collector`. + /// + /// [`Handle`]: crate::Handle pub fn handle(&self) -> Handle { unsafe { (*self.inner).handles.fetch_add(1, Ordering::Relaxed); @@ -120,10 +201,59 @@ impl Collector { Handle { collector: self.inner } } + /// Drops all of the garbage in the queue. + /// + /// # Examples + /// ``` + /// use basedrop::{Collector, Handle, Owned}; + /// use core::mem::drop; + /// + /// let mut collector = Collector::new(); + /// let handle = collector.handle(); + /// let x = Owned::new(&handle, 1); + /// let y = Owned::new(&handle, 2); + /// let z = Owned::new(&handle, 3); + /// + /// assert_eq!(collector.alloc_count(), 3); + /// + /// drop(x); + /// drop(y); + /// drop(z); + /// collector.collect(); + /// + /// assert_eq!(collector.alloc_count(), 0); + /// ``` pub fn collect(&mut self) { while self.collect_one() {} } + /// Attempts to drop the first allocation in the queue. If successful, + /// returns true; otherwise returns false. + /// + /// # Examples + /// ``` + /// use basedrop::{Collector, Handle, Owned}; + /// use core::mem::drop; + /// + /// let mut collector = Collector::new(); + /// let handle = collector.handle(); + /// let x = Owned::new(&handle, 1); + /// let y = Owned::new(&handle, 2); + /// let z = Owned::new(&handle, 3); + /// + /// assert_eq!(collector.alloc_count(), 3); + /// + /// drop(x); + /// drop(y); + /// drop(z); + /// + /// assert!(collector.collect_one()); + /// assert!(collector.collect_one()); + /// assert!(collector.collect_one()); + /// + /// assert!(!collector.collect_one()); + /// assert_eq!(collector.alloc_count(), 0); + /// ``` pub fn collect_one(&mut self) -> bool { loop { unsafe { @@ -147,14 +277,43 @@ impl Collector { } } + /// Gets the number of live [`Handle`]s to this `Collector`. + /// + /// [`Handle`]: crate::Handle pub fn handle_count(&self) -> usize { unsafe { (*self.inner).handles.load(Ordering::Relaxed) } } + /// Gets the number of live allocations associated with this `Collector`. pub fn alloc_count(&self) -> usize { unsafe { (*self.inner).allocs.load(Ordering::Relaxed) } } + /// Attempts to free all resources associated with this `Collector`. This + /// method will fail and return the original `Collector` if there are any + /// live [`Handle`]s or allocations associated with it. + /// + /// # Examples + /// ``` + /// use basedrop::{Collector, Handle, Owned}; + /// use core::mem::drop; + /// + /// let mut collector = Collector::new(); + /// let handle = collector.handle(); + /// let x = Owned::new(&handle, 3); + /// + /// let result = collector.try_cleanup(); + /// assert!(result.is_err()); + /// let mut collector = result.unwrap_err(); + /// + /// drop(handle); + /// drop(x); + /// collector.collect(); + /// + /// assert!(collector.try_cleanup().is_ok()); + /// ``` + /// + /// [`Handle`]: crate::Handle pub fn try_cleanup(self) -> Result<(), Self> { unsafe { if (*self.inner).handles.load(Ordering::Acquire) == 0 { diff --git a/src/lib.rs b/src/lib.rs index 68b098a..2b75d89 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,20 @@ +//! Memory-management tools for real-time audio and other latency-critical scenarios. +//! +//! - [`Owned`] and [`Shared`] are smart pointers analogous to `Box` and `Arc` +//! which add their contents to a queue for deferred collection when dropped. +//! - [`Collector`] is used to process the drop queue. +//! - [`Node`] provides a lower-level interface for implementing custom smart +//! pointers or data structures. +//! - [`SharedCell`] implements a mutable memory location holding a [`Shared`] +//! pointer that can be used by multiple readers and writers in a thread-safe +//! manner. +//! +//! [`Owned`]: crate::Owned +//! [`Shared`]: crate::Shared +//! [`Collector`]: crate::Collector +//! [`Node`]: crate::Node +//! [`SharedCell`]: crate::SharedCell + #![no_std] mod collector; diff --git a/src/owned.rs b/src/owned.rs index ba7b7e3..0fc154a 100644 --- a/src/owned.rs +++ b/src/owned.rs @@ -4,6 +4,15 @@ use core::marker::PhantomData; use core::ops::{Deref, DerefMut}; use core::ptr::NonNull; +/// An owned smart pointer with deferred collection, analogous to `Box`. +/// +/// When an `Owned` is dropped, its contents are added to the drop queue +/// of the [`Collector`] whose [`Handle`] it was originally allocated with. +/// As the collector may be on another thread, contents are required to be +/// `Send + 'static`. +/// +/// [`Collector`]: crate::Collector +/// [`Handle`]: crate::Handle pub struct Owned { node: NonNull>, phantom: PhantomData, @@ -13,6 +22,15 @@ unsafe impl Send for Owned {} unsafe impl Sync for Owned {} impl Owned { + /// Constructs a new `Owned`. + /// + /// # Examples + /// ``` + /// use basedrop::{Collector, Owned}; + /// + /// let collector = Collector::new(); + /// let three = Owned::new(&collector.handle(), 3); + /// ``` pub fn new(handle: &Handle, data: T) -> Owned { Owned { node: unsafe { NonNull::new_unchecked(Node::alloc(handle, data)) }, diff --git a/src/shared.rs b/src/shared.rs index 9fff558..e55777c 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -5,6 +5,16 @@ use core::ops::Deref; use core::ptr::NonNull; use core::sync::atomic::{AtomicUsize, Ordering, fence}; +/// A reference-counted smart pointer with deferred collection, analogous to +/// `Arc`. +/// +/// When a `Shared`'s reference count goes to zero, its contents are added +/// to the drop queue of the [`Collector`] whose [`Handle`] it was originally +/// allocated with. As the collector may be on another thread, contents are +/// required to be `Send + 'static`. +/// +/// [`Collector`]: crate::Collector +/// [`Handle`]: crate::Handle pub struct Shared { pub(crate) node: NonNull>>, pub(crate) phantom: PhantomData>, @@ -19,6 +29,15 @@ unsafe impl Send for Shared {} unsafe impl Sync for Shared {} impl Shared { + /// Constructs a new `Shared`. + /// + /// # Examples + /// ``` + /// use basedrop::{Collector, Shared}; + /// + /// let collector = Collector::new(); + /// let three = Shared::new(&collector.handle(), 3); + /// ``` pub fn new(handle: &Handle, data: T) -> Shared { Shared { node: unsafe { @@ -33,6 +52,23 @@ impl Shared { } impl Shared { + /// Returns a mutable reference to the contained value if there are no + /// other extant `Shared` pointers to the same allocation; otherwise + /// returns `None`. + /// + /// # Examples + /// ``` + /// use basedrop::{Collector, Shared}; + /// + /// let collector = Collector::new(); + /// let mut x = Shared::new(&collector.handle(), 3); + /// + /// *Shared::get_mut(&mut x).unwrap() = 4; + /// assert_eq!(*x, 4); + /// + /// let _y = Shared::clone(&x); + /// assert!(Shared::get_mut(&mut x).is_none()); + /// ``` pub fn get_mut(this: &mut Self) -> Option<&mut T> { unsafe { if this.node.as_ref().data.count.load(Ordering::Acquire) == 1 { diff --git a/src/shared_cell.rs b/src/shared_cell.rs index 5d08f51..c4cbda5 100644 --- a/src/shared_cell.rs +++ b/src/shared_cell.rs @@ -4,6 +4,12 @@ use core::marker::PhantomData; use core::ptr::NonNull; use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; +/// A thread-safe shared mutable memory location that holds a [`Shared`]. +/// +/// `SharedCell` is designed to be low-overhead for readers at the expense of +/// somewhat higher overhead for writers. +/// +/// [`Shared`]: crate::Shared pub struct SharedCell { readers: AtomicUsize, node: AtomicPtr>>, @@ -14,6 +20,16 @@ unsafe impl Send for SharedCell {} unsafe impl Sync for SharedCell {} impl SharedCell { + /// Constructs a new `SharedCell` containing `value`. + /// + /// # Examples + /// ``` + /// use basedrop::{Collector, Shared, SharedCell}; + /// + /// let collector = Collector::new(); + /// let three = Shared::new(&collector.handle(), 3); + /// let cell = SharedCell::new(three); + /// ``` pub fn new(value: Shared) -> SharedCell { SharedCell { readers: AtomicUsize::new(0), @@ -24,6 +40,21 @@ impl SharedCell { } impl SharedCell { + /// Gets a copy of the contained [`Shared`], incrementing its reference + /// count in the process. + /// + /// # Examples + /// ``` + /// use basedrop::{Collector, Shared, SharedCell}; + /// + /// let collector = Collector::new(); + /// let x = Shared::new(&collector.handle(), 3); + /// let cell = SharedCell::new(x); + /// + /// let y = cell.get(); + /// ``` + /// + /// [`Shared`]: crate::Shared pub fn get(&self) -> Shared { self.readers.fetch_add(1, Ordering::SeqCst); let node = self.node.load(Ordering::SeqCst); @@ -34,11 +65,42 @@ impl SharedCell { } } + /// Replaces the contained [`Shared`], decrementing its reference count + /// in the process. + /// + /// # Examples + /// ``` + /// use basedrop::{Collector, Shared, SharedCell}; + /// + /// let collector = Collector::new(); + /// let x = Shared::new(&collector.handle(), 3); + /// let cell = SharedCell::new(x); + /// + /// let y = Shared::new(&collector.handle(), 4); + /// cell.set(y); + /// ``` + /// + /// [`Shared`]: crate::Shared pub fn set(&self, value: Shared) { let old = self.replace(value); core::mem::drop(old); } + /// Replaces the contained [`Shared`] and returns it. + /// + /// # Examples + /// ``` + /// use basedrop::{Collector, Shared, SharedCell}; + /// + /// let collector = Collector::new(); + /// let x = Shared::new(&collector.handle(), 3); + /// let cell = SharedCell::new(x); + /// + /// let y = Shared::new(&collector.handle(), 4); + /// let x = cell.replace(y); + /// ``` + /// + /// [`Shared`]: crate::Shared pub fn replace(&self, value: Shared) -> Shared { let old = self.node.swap(value.node.as_ptr(), Ordering::AcqRel); while self.readers.load(Ordering::Relaxed) != 0 {} @@ -48,6 +110,22 @@ impl SharedCell { } } + /// Consumes the `SharedCell` and returns the contained [`Shared`]. This + /// is safe because we are guaranteed to be the only holder of the + /// `SharedCell`. + /// + /// # Examples + /// ``` + /// use basedrop::{Collector, Shared, SharedCell}; + /// + /// let collector = Collector::new(); + /// let x = Shared::new(&collector.handle(), 3); + /// let cell = SharedCell::new(x); + /// + /// let x = cell.into_inner(); + /// ``` + /// + /// [`Shared`]: crate::Shared pub fn into_inner(mut self) -> Shared { let node = core::mem::replace(&mut self.node, AtomicPtr::new(core::ptr::null_mut())); core::mem::forget(self);