Skip to content

Commit

Permalink
docs(cordyceps): add basic linked list examples (#200)
Browse files Browse the repository at this point in the history
* docs(cordyceps): add basic linked list examples
* misc docs fixy-uppy

Signed-off-by: Eliza Weisman <[email protected]>
  • Loading branch information
hawkw authored Jun 6, 2022
1 parent c555772 commit 05c1509
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 8 deletions.
9 changes: 6 additions & 3 deletions cordyceps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,28 @@ implemented for the [Mycelium] operating system. Currently, it provides an
[intrusive doubly-linked list][list] and an [intrusive, lock-free MPSC
queue][queue].

## intrusive data structures

[Intrusive data structures][intrusive] are node-based data structures where the
node data (pointers to other nodes and, potentially, any associated metadata)
are stored _within_ the values that are contained by the data structure, rather
than owning those values.

## when should i use intrusive data structures?
### when should i use intrusive data structures?

- Because node data is stored *inside* of the elements of a collection, no
additional heap allocation is required for those nodes. This means that when
an element is already heap allocated, it can be added to a collection without
requiring an additional allcoation.
requiring an additional allocation.
- Similarly, when elements are at fixed memory locations (such as pages in a
page allocator, or `static`s), they can be added to intrusive data structures
without allocating *at all*. This makes intrusive data structures useful in
code that cannot allocate &mdash; for example, we might use intrusive lists of
memory regions to *implement* a heap allocator.
- Intrusive data structures may offer better performance than other linked or
node-based data structures, since allocator overhead is avoided.
## when shouldn't i use intrusive data structures?

### when shouldn't i use intrusive data structures?

- Intrusive data structures require the elements stored in a collection to be
_aware_ of the collection. If a `struct` is to be stored in an intrusive
Expand Down
3 changes: 2 additions & 1 deletion cordyceps/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub(crate) mod util;

use core::ptr::NonNull;

/// Trait implemented by types which can be members of an intrusive collection.
/// Trait implemented by types which can be members of an [intrusive collection].
///
/// In order to be part of an intrusive collection, a type must contain a
/// `Links` type that stores the pointers to other nodes in that collection. For
Expand All @@ -57,6 +57,7 @@ use core::ptr::NonNull;
/// Failure to uphold these invariants will result in corruption of the
/// intrusive data structure, including dangling pointers.
///
/// [intrusive collection]: crate#intrusive-data-structures
/// [`Unpin`]: core::marker::Unpin
/// [doubly-linked list]: crate::list
/// [MSPC queue]: crate::mpsc_queue
Expand Down
180 changes: 179 additions & 1 deletion cordyceps/src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use core::{
ptr::{self, NonNull},
};

/// An intrusive doubly-linked list.
/// An [intrusive] doubly-linked list.
///
/// This data structure may be used as a first-in, first-out queue by using the
/// [`List::push_front`] and [`List::pop_back`] methods. It also supports
Expand All @@ -23,6 +23,184 @@ use core::{
/// In order to be part of a `List`, a type `T` must implement [`Linked`] for
/// [`list::Links<T>`].
///
/// # Examples
///
/// Implementing the [`Linked`] trait for an entry type:
///
/// ```
/// use cordyceps::{
/// Linked,
/// list::{self, List},
/// };
///
/// // This example uses the Rust standard library for convenience, but
/// // the doubly-linked list itself does not require std.
/// use std::{pin::Pin, ptr::NonNull, thread, sync::Arc};
///
/// /// A simple queue entry that stores an `i32`.
/// // This type must be `repr(C)` in order for the cast in `Linked::links`
/// // to be sound.
/// #[repr(C)]
/// #[derive(Debug, Default)]
/// struct Entry {
/// links: list::Links<Entry>,
/// val: i32,
/// }
///
/// // Implement the `Linked` trait for our entry type so that it can be used
/// // as a queue entry.
/// unsafe impl Linked<list::Links<Entry>> for Entry {
/// // In this example, our entries will be "owned" by a `Box`, but any
/// // heap-allocated type that owns an element may be used.
/// //
/// // An element *must not* move while part of an intrusive data
/// // structure. In many cases, `Pin` may be used to enforce this.
/// type Handle = Pin<Box<Self>>;
///
/// /// Convert an owned `Handle` into a raw pointer
/// fn into_ptr(handle: Pin<Box<Entry>>) -> NonNull<Entry> {
/// unsafe { NonNull::from(Box::leak(Pin::into_inner_unchecked(handle))) }
/// }
///
/// /// Convert a raw pointer back into an owned `Handle`.
/// unsafe fn from_ptr(ptr: NonNull<Entry>) -> Pin<Box<Entry>> {
/// // Safety: if this function is only called by the linked list
/// // implementation (and it is not intended for external use), we can
/// // expect that the `NonNull` was constructed from a reference which
/// // was pinned.
/// //
/// // If other callers besides `List`'s internals were to call this on
/// // some random `NonNull<Entry>`, this would not be the case, and
/// // this could be constructing an erroneous `Pin` from a referent
/// // that may not be pinned!
/// Pin::new_unchecked(Box::from_raw(ptr.as_ptr()))
/// }
///
/// /// Access an element's `Links`.
/// unsafe fn links(target: NonNull<Entry>) -> NonNull<list::Links<Entry>> {
/// // Safety: this cast is safe only because `Entry` `is repr(C)` and
/// // the links is the first field.
/// target.cast()
/// }
/// }
///
/// impl Entry {
/// fn new(val: i32) -> Self {
/// Self {
/// val,
/// ..Self::default()
/// }
/// }
/// }
/// ```
///
/// Using a `List` as a first-in, first-out (FIFO) queue with
/// [`List::push_back`] and [`List::pop_front`]:
/// ```
/// # use cordyceps::{
/// # Linked,
/// # list::{self, List},
/// # };
/// # use std::{pin::Pin, ptr::NonNull, thread, sync::Arc};
/// # #[repr(C)]
/// # #[derive(Debug, Default)]
/// # struct Entry {
/// # links: list::Links<Entry>,
/// # val: i32,
/// # }
/// # unsafe impl Linked<list::Links<Entry>> for Entry {
/// # type Handle = Pin<Box<Self>>;
/// # fn into_ptr(handle: Pin<Box<Entry>>) -> NonNull<Entry> {
/// # unsafe { NonNull::from(Box::leak(Pin::into_inner_unchecked(handle))) }
/// # }
/// # unsafe fn from_ptr(ptr: NonNull<Entry>) -> Pin<Box<Entry>> {
/// # Pin::new_unchecked(Box::from_raw(ptr.as_ptr()))
/// # }
/// # unsafe fn links(target: NonNull<Entry>) -> NonNull<list::Links<Entry>> {
/// # target.cast()
/// # }
/// # }
/// # impl Entry {
/// # fn new(val: i32) -> Self {
/// # Self {
/// # val,
/// # ..Self::default()
/// # }
/// # }
/// # }
/// // Now that we've implemented the `Linked` trait for our `Entry` type, we can
/// // create a `List` of entries:
/// let mut list = List::<Entry>::new();
///
/// // Push some entries to the list:
/// for i in 0..5 {
/// list.push_back(Box::pin(Entry::new(i)));
/// }
///
/// // The list is a doubly-ended queue. We can use the `pop_front` method with
/// // `push_back` to dequeue elements in FIFO order:
/// for i in 0..5 {
/// let entry = list.pop_front()
/// .expect("the list should have 5 entries in it");
/// assert_eq!(entry.val, i, "entries are dequeued in FIFO order");
/// }
///
/// assert!(list.is_empty());
/// ```
///
/// Using a `List` as a last-in, first-out (LIFO) stack with
/// [`List::push_back`] and [`List::pop_back`]:
/// ```
/// # use cordyceps::{
/// # Linked,
/// # list::{self, List},
/// # };
/// # use std::{pin::Pin, ptr::NonNull, thread, sync::Arc};
/// # #[repr(C)]
/// # #[derive(Debug, Default)]
/// # struct Entry {
/// # links: list::Links<Entry>,
/// # val: i32,
/// # }
/// # unsafe impl Linked<list::Links<Entry>> for Entry {
/// # type Handle = Pin<Box<Self>>;
/// # fn into_ptr(handle: Pin<Box<Entry>>) -> NonNull<Entry> {
/// # unsafe { NonNull::from(Box::leak(Pin::into_inner_unchecked(handle))) }
/// # }
/// # unsafe fn from_ptr(ptr: NonNull<Entry>) -> Pin<Box<Entry>> {
/// # Pin::new_unchecked(Box::from_raw(ptr.as_ptr()))
/// # }
/// # unsafe fn links(target: NonNull<Entry>) -> NonNull<list::Links<Entry>> {
/// # target.cast()
/// # }
/// # }
/// # impl Entry {
/// # fn new(val: i32) -> Self {
/// # Self {
/// # val,
/// # ..Self::default()
/// # }
/// # }
/// # }
/// let mut list = List::<Entry>::new();
///
/// // Push some entries to the list:
/// for i in 0..5 {
/// list.push_back(Box::pin(Entry::new(i)));
/// }
///
/// // Note that we have reversed the direction of the iterator, since
/// // we are popping from the *back* of the list:
/// for i in (0..5).into_iter().rev() {
/// let entry = list.pop_back()
/// .expect("the list should have 5 entries in it");
/// assert_eq!(entry.val, i, "entries are dequeued in LIFO order");
/// }
///
/// assert!(list.is_empty());
/// ```
///
/// [intrusive]: crate#intrusive-data-structures
/// [`list::Links<T>`]: crate::list::Links
pub struct List<T: ?Sized> {
head: Link<T>,
Expand Down
8 changes: 5 additions & 3 deletions cordyceps/src/mpsc_queue.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
//! A multi-producer, single-consumer (MPSC) queue, implemented using a
//! lock-free intrusive singly-linked list.
//! lock-free [intrusive] singly-linked list.
//!
//! See the documentation for the [`MpscQueue`] type for details.
//!
//! Based on [Dmitry Vyukov's intrusive MPSC][vyukov].
//!
//! [vyukov]: http://www.1024cores.net/home/lock-free-algorithms/queues/intrusive-mpsc-node-based-queue
//! [intrusive]: crate#intrusive-data-structures
use crate::{
loom::{
cell::UnsafeCell,
Expand All @@ -21,7 +22,7 @@ use core::{
};

/// A multi-producer, single-consumer (MPSC) queue, implemented using a
/// lock-free intrusive singly-linked list.
/// lock-free [intrusive] singly-linked list.
///
/// Based on [Dmitry Vyukov's intrusive MPSC][vyukov].
///
Expand Down Expand Up @@ -74,7 +75,7 @@ use core::{
/// // expect that the `NonNull` was constructed from a reference which
/// // was pinned.
/// //
/// // If other callers besides `List`'s internals were to call this on
/// // If other callers besides `MpscQueue`'s internals were to call this on
/// // some random `NonNull<Entry>`, this would not be the case, and
/// // this could be constructing an erroneous `Pin` from a referent
/// // that may not be pinned!
Expand Down Expand Up @@ -333,6 +334,7 @@ use core::{
/// [`Consumer::try_dequeue`] methods will instead return [an error] when the
/// queue is in an inconsistent state.
///
/// [intrusive]: crate#intrusive-data-structures
/// [vyukov]: http://www.1024cores.net/home/lock-free-algorithms/queues/intrusive-mpsc-node-based-queue
/// [`dequeue`]: Self::dequeue
/// [`enqueue`]: Self::enqueue
Expand Down

0 comments on commit 05c1509

Please sign in to comment.