nexus_logbuf/lib.rs
1//! High-performance lock-free ring buffers for variable-length messages.
2//!
3//! This crate provides bounded SPSC and MPSC byte ring buffers optimized for
4//! getting data off the hot path without disturbing it. No allocation, no
5//! formatting, no syscalls on the producer side.
6//!
7//! # Modules
8//!
9//! - [`queue`]: Low-level ring buffer primitives. No blocking, maximum control.
10//! - [`channel`]: Ergonomic channel API with backoff and parking for receivers.
11//!
12//! # Design
13//!
14//! - **Flat byte buffer** with free-running offsets, power-of-2 capacity
15//! - **len-as-commit**: Record's len field is the commit marker (non-zero = ready)
16//! - **Skip markers**: High bit of len distinguishes padding/aborted claims
17//! - **Consumer zeroing**: Consumer zeros records before releasing space
18//! - **Claim-based API**: `WriteClaim`/`ReadClaim` with RAII semantics
19//!
20//! # Channel Philosophy
21//!
22//! **Senders are never slowed down.** They use brief backoff (spin + yield) but
23//! never syscall. If the buffer is full, they return an error immediately.
24//!
25//! **Receivers can block.** They use `park_timeout` to wait for messages without
26//! burning CPU, but always with a timeout to check for disconnection.
27//!
28//! # Example (Queue API)
29//!
30//! ```
31//! use nexus_logbuf::queue::spsc;
32//!
33//! let (mut producer, mut consumer) = spsc::new(4096);
34//!
35//! // Producer (hot path)
36//! let payload = b"hello world";
37//! if let Ok(mut claim) = producer.try_claim(payload.len()) {
38//! claim.copy_from_slice(payload);
39//! claim.commit();
40//! }
41//!
42//! // Consumer (background thread)
43//! if let Some(record) = consumer.try_claim() {
44//! assert_eq!(&*record, b"hello world");
45//! // record dropped here -> zeros region, advances head
46//! }
47//! ```
48
49pub mod channel;
50pub mod queue;
51
52// Re-export for convenience (queue is the primitive layer)
53pub use queue::mpsc;
54pub use queue::spsc;
55
56/// Error returned from queue `try_claim` operations when the buffer has no
57/// space for the requested record.
58///
59/// This is the only failure mode that `try_claim` can surface as an error:
60/// passing `len == 0` is a precondition violation and panics (`len == 0` is
61/// reserved by the wire format as the "uncommitted" sentinel).
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub struct BufferFull;
64
65impl std::fmt::Display for BufferFull {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 f.write_str("buffer full")
68 }
69}
70
71impl std::error::Error for BufferFull {}
72
73/// Align a value up to the next multiple of 8.
74#[inline]
75pub(crate) const fn align8(n: usize) -> usize {
76 (n + 7) & !7
77}
78
79/// Record header constants.
80///
81/// The len field is a `usize` (system word) and uses the high bit as a skip
82/// marker:
83/// - `len == 0`: Not committed, consumer waits
84/// - `len > 0, high bit clear`: Committed record, payload is `len` bytes
85/// - `len high bit set`: Skip marker, advance by `len & LEN_MASK` bytes
86pub(crate) const SKIP_BIT: usize = 1 << (usize::BITS - 1);
87pub(crate) const LEN_MASK: usize = !SKIP_BIT;
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn align8_works() {
95 assert_eq!(align8(0), 0);
96 assert_eq!(align8(1), 8);
97 assert_eq!(align8(7), 8);
98 assert_eq!(align8(8), 8);
99 assert_eq!(align8(9), 16);
100 assert_eq!(align8(15), 16);
101 assert_eq!(align8(16), 16);
102 }
103}