Skip to main content

buffer_sv2/
lib.rs

1//! # `buffer_sv2`
2//!
3//! Handles memory management for Stratum V2 (Sv2) roles.
4//!
5//! Provides a memory-efficient buffer pool ([`BufferPool`]) that minimizes allocations and
6//! deallocations for high-throughput message frame processing in Sv2 roles. [`Slice`] helps
7//! minimize memory allocation overhead by reusing large buffers, improving performance and
8//! reducing latency. The [`BufferPool`] tracks the usage of memory slices, using atomic operations
9//! and shared state tracking to safely manage memory across multiple threads.
10//!
11//! ## Memory Structure
12//!
13//! The [`BufferPool`] manages a contiguous block of memory allocated on the heap, divided into
14//! fixed-size slots. Memory allocation within this pool operates in three distinct modes:
15//!
16//! 1. **Back Mode**: By default, memory is allocated sequentially from the back (end) of the buffer
17//!    pool. This mode continues until the back slots are fully occupied.
18//! 2. **Front Mode**: Once the back slots are exhausted, the [`BufferPool`] checks if any slots at
19//!    the front (beginning) have been freed. If available, it switches to front mode, allocating
20//!    memory from the front slots.
21//! 3. **Alloc Mode**: If both back and front slots are occupied, the [`BufferPool`] enters alloc
22//!    mode, where it allocates additional memory directly from the system heap. This mode may
23//!    introduce performance overhead due to dynamic memory allocation.
24//!
25//! [`BufferPool`] dynamically transitions between these modes based on slot availability,
26//! optimizing memory usage and performance.
27//!
28//! ## Usage
29//!
30//! When an incoming Sv2 message is received, it is buffered for processing. The [`BufferPool`]
31//! attempts to allocate memory from its internal slots, starting in back mode. If the back slots
32//! are full, it checks for available front slots to switch to front mode. If no internal slots are
33//! free, it resorts to alloc mode, allocating memory from the system heap.
34//!
35//! For operations requiring dedicated buffers, the [`Slice`] type manages its own memory using
36//! [`Vec<u8>`](alloc::vec::Vec). In high-performance scenarios, [`Slice`] can reference externally
37//! managed memory from the [`BufferPool`], reducing dynamic memory allocations and increasing
38//! performance.
39//!
40//! ## Slices are scratch space
41//!
42//! A [`Slice`] handed out by a [`BufferPool`] holds one of its slots until it is dropped. The pool
43//! is meant for frames that are decoded and then dropped: a slice kept alive longer keeps its
44//! slot, and once every slot is held, each new frame falls back to system memory. Bytes that have
45//! to outlive decoding are copied out: cloning a [`Slice`] copies it into memory the clone owns,
46//! and the slot is freed as soon as the original is dropped.
47//!
48//! ### Debug Mode
49//! Provides additional tracking for debugging memory management issues.
50
51#![cfg_attr(not(feature = "debug"), no_std)]
52
53mod buffer;
54mod buffer_pool;
55mod slice;
56#[cfg(test)]
57mod test;
58
59extern crate alloc;
60
61pub use crate::buffer::BufferFromSystemMemory;
62pub use buffer_pool::BufferPool;
63pub use slice::Slice;
64
65/// Interface for working with memory buffers.
66///
67/// Implemented by [`BufferPool`], which hands out slices of preallocated memory, and by
68/// [`BufferFromSystemMemory`], which hands out [`Vec<u8>`](alloc::vec::Vec)s. Utilities are
69/// provided to reserve writable memory, read back the frame being written, and hand it out.
70///
71/// The encoders and decoders of the [`codec_sv2` crate](https://crates.io/crates/codec_sv2) write
72/// frames through this trait.
73pub trait Buffer {
74    /// The type of slice that the buffer uses.
75    type Slice: AsMut<[u8]> + AsRef<[u8]> + Into<Slice>;
76
77    /// Makes room for `len` more bytes after the data written so far and returns that space,
78    /// without counting any of it as written.
79    ///
80    /// Only [`Buffer::commit`] counts bytes as written. Reserving again before committing
81    /// replaces the reservation, and bytes written into it are not kept.
82    fn reserve(&mut self, len: usize) -> &mut [u8];
83
84    /// Counts the first `len` bytes of the last reservation as written, and ends that
85    /// reservation.
86    ///
87    /// Panics if `len` is larger than the last reservation.
88    fn commit(&mut self, len: usize);
89
90    /// Provides ownership of a slice in the buffer pool to the caller and updates the buffer
91    /// pool's state by modifying the position in `shared_state` that the slice occupies. The pool
92    /// now points to the next set of uninitialized space.
93    fn get_data_owned(&mut self) -> Self::Slice;
94
95    /// Returns the committed bytes of the frame being written, without transferring ownership of
96    /// the buffer.
97    fn frame(&self) -> &[u8];
98
99    /// Returns the committed bytes of the frame being written, mutably, without transferring
100    /// ownership of the buffer.
101    fn frame_mut(&mut self) -> &mut [u8];
102
103    /// Returns the size of the written portion of the buffer. This is useful for tracking how much
104    /// of the buffer has been filled with data. The number of bytes currently written in the
105    /// buffer is returned.
106    fn len(&self) -> usize;
107
108    /// Drops the committed bytes past `len`, like [`Vec::truncate`](alloc::vec::Vec::truncate): a
109    /// `len` at or past the committed length changes nothing, so the frame can only shrink.
110    fn truncate(&mut self, len: usize);
111
112    /// Returns `true` if the buffer is empty, `false` otherwise.
113    fn is_empty(&self) -> bool {
114        self.len() == 0
115    }
116}