Skip to content

Commit

Permalink
docs
Browse files Browse the repository at this point in the history
  • Loading branch information
micahrj committed Jan 18, 2021
1 parent a136fb6 commit 8d70b79
Show file tree
Hide file tree
Showing 6 changed files with 309 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
159 changes: 159 additions & 0 deletions src/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,20 @@ union NodeLink {
next: ManuallyDrop<AtomicPtr<Node<()>>>,
}

/// 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<T> {
link: NodeLink,
drop: unsafe fn(*mut Node<()>),
/// The data stored in this allocation.
pub data: T,
}

Expand All @@ -20,6 +31,19 @@ unsafe fn drop_node<T>(node: *mut Node<()>) {
}

impl<T: Send + 'static> Node<T> {
/// 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<T> {
unsafe {
(*handle.collector).allocs.fetch_add(1, Ordering::Relaxed);
Expand All @@ -36,20 +60,63 @@ impl<T: Send + 'static> Node<T> {
}

impl<T> Node<T> {
/// 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<T>) {
let collector = (*node).link.collector;
(*node).link.next = ManuallyDrop::new(AtomicPtr::new(core::ptr::null_mut()));
let tail = (*collector).tail.swap(node as *mut Node<()>, Ordering::AcqRel);
(*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<T>) -> Handle {
let collector = (*node).link.collector;
(*collector).handles.fetch_add(1, Ordering::Relaxed);
Handle { collector }
}
}

/// 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,
}
Expand Down Expand Up @@ -81,6 +148,16 @@ struct CollectorInner {
tail: AtomicPtr<Node<()>>,
}

/// 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<()>,
Expand All @@ -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 {
Expand All @@ -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);
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
17 changes: 17 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
18 changes: 18 additions & 0 deletions src/owned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>` 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<T> {
node: NonNull<Node<T>>,
phantom: PhantomData<T>,
Expand All @@ -13,6 +22,15 @@ unsafe impl<T: Send> Send for Owned<T> {}
unsafe impl<T: Sync> Sync for Owned<T> {}

impl<T: Send + 'static> Owned<T> {
/// Constructs a new `Owned<T>`.
///
/// # Examples
/// ```
/// use basedrop::{Collector, Owned};
///
/// let collector = Collector::new();
/// let three = Owned::new(&collector.handle(), 3);
/// ```
pub fn new(handle: &Handle, data: T) -> Owned<T> {
Owned {
node: unsafe { NonNull::new_unchecked(Node::alloc(handle, data)) },
Expand Down
36 changes: 36 additions & 0 deletions src/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>`'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<T> {
pub(crate) node: NonNull<Node<SharedInner<T>>>,
pub(crate) phantom: PhantomData<SharedInner<T>>,
Expand All @@ -19,6 +29,15 @@ unsafe impl<T: Send + Sync> Send for Shared<T> {}
unsafe impl<T: Send + Sync> Sync for Shared<T> {}

impl<T: Send + 'static> Shared<T> {
/// Constructs a new `Shared<T>`.
///
/// # Examples
/// ```
/// use basedrop::{Collector, Shared};
///
/// let collector = Collector::new();
/// let three = Shared::new(&collector.handle(), 3);
/// ```
pub fn new(handle: &Handle, data: T) -> Shared<T> {
Shared {
node: unsafe {
Expand All @@ -33,6 +52,23 @@ impl<T: Send + 'static> Shared<T> {
}

impl<T> Shared<T> {
/// 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 {
Expand Down
Loading

0 comments on commit 8d70b79

Please sign in to comment.