ringbuf 0.6.0

Lock-free SPSC FIFO ring buffer with direct access to inner data
Documentation

ringbuf

Crates.io Docs.rs Github Actions Gitlab CI License

Lock-free SPSC FIFO ring buffer with direct access to inner data.

Features

  • Lock-free operations - they succeed or fail immediately without blocking or waiting.
  • Arbitrary item type (not only Copy).
  • Items can be inserted and removed one by one or many at once.
  • Thread-safe direct access to the internal ring buffer memory.
  • Read and Write implementation.
  • Overwriting insertion support.
  • Different types of buffers and underlying storages.
  • Can be used without std and even without alloc (using only statically-allocated memory).
  • Async and blocking versions (see this section).
  • Can optionally use the portable-atomic crate to allow usage on smaller systems without CAS operations.

Standalone buffer access

Backing buffers provide inherent methods such as try_push, try_pop, push_slice, and pop_iter. All data access on the backing buffer requires &mut self, including read-only views. Metadata supports shared access. Consumer endpoints provide read-only views through &self.

The Producer and Consumer traits are implemented by endpoints and the exclusive adapter. Pass &mut rb.as_exclusive() to generic producer/consumer algorithms. Shared backing handles support metadata observation and checked endpoint acquisition. Direct endpoints are Send when their backing handle and items are Send, and Sync when both are Sync. See buffer ownership and data views.

Usage

At first you need to create the ring buffer itself. HeapRb is recommended but you may choose another one.

The ring buffer can be split into a pair of Producer and Consumer endpoints. Producer is used to insert items to the ring buffer, consumer - to remove items from it.

Types

There are several types of ring buffers provided:

  • LocalRb. Only for single-threaded use.
  • SharedRb. Can be shared between threads. Its frequently used instances:
    • HeapRb. Contents are stored in dynamic memory. Recommended for use in most cases.
    • StaticRb. Contents can be stored in statically-allocated memory.

You may also provide your own generic parameters.

Performance

SharedRb needs to synchronize CPU cache between CPU cores. This synchronization has some overhead. To avoid multiple unnecessary synchronizations you may use methods that operate many items at once(push_slice/push_iter, pop_slice, etc.). Caching endpoints also avoid repeatedly fetching the opposite endpoint's index when progress is possible. All completed operations publish their index updates immediately, including each step of pop_iter. If an iterator panics during push_iter, the initialized prefix is published and can be consumed.

skip and clear take constant time for items without destructors, such as u8. Items that need destruction are dropped individually, with each slot kept occupied until its destructor finishes.

For single-threaded usage LocalRb is recommended because it is slightly faster than SharedRb due to absence of CPU cache synchronization.

PopIter yields only the items present when it is created and releases each slot as it yields the item. Use pop_slice to batch removals.

Examples

Simple

use ringbuf::{traits::*, HeapRb};

let rb = HeapRb::<i32>::new(2);
let (mut prod, mut cons) = rb.split();

prod.try_push(0).unwrap();
prod.try_push(1).unwrap();
assert_eq!(prod.try_push(2), Err(2));

assert_eq!(cons.try_pop(), Some(0));

prod.try_push(2).unwrap();

assert_eq!(cons.try_pop(), Some(1));
assert_eq!(cons.try_pop(), Some(2));
assert_eq!(cons.try_pop(), None);

No heap

use ringbuf::{traits::*, StaticRb};

const RB_SIZE: usize = 1;
let mut rb = StaticRb::<i32, RB_SIZE>::default();
let (mut prod, mut cons) = rb.split_ref();

assert_eq!(prod.try_push(123), Ok(()));
assert_eq!(prod.try_push(321), Err(321));

assert_eq!(cons.try_pop(), Some(123));
assert_eq!(cons.try_pop(), None);

Overwrite

Ring buffer can be used in overwriting mode when insertion overwrites the oldest element if the buffer is full.

use ringbuf::{traits::*, HeapRb};

let mut rb = HeapRb::<i32>::new(2);

assert_eq!(rb.push_overwrite(0), None);
assert_eq!(rb.push_overwrite(1), None);
assert_eq!(rb.push_overwrite(2), Some(0));

assert_eq!(rb.try_pop(), Some(1));
assert_eq!(rb.try_pop(), Some(2));
assert_eq!(rb.try_pop(), None);

Note that push_overwrite requires exclusive access to the ring buffer so to perform it concurrently you need to guard the ring buffer with mutex or some other lock.

Implementation details

Capacity

Capacity must be in 1..=usize::MAX / 2, including for zero-sized items. new panics if the capacity is zero or exceeds this limit. Heap buffers also provide try_new, which returns CreateError::Capacity for invalid capacity or CreateError::Allocation when storage allocation fails.

Implementing custom endpoints

Custom Producer and Consumer implementations require unsafe impl Raw. The Raw trait contains storage access, cursor updates, and ownership bookkeeping; its safety contract also covers the consistency of safe method overrides. Observer is a safe, metadata-only interface. Delegating wrappers implement unsafe impl DelegateRaw in addition to their Delegate* traits. Import ringbuf::traits::Raw to use raw trait operations.

Derived crates

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.