Skip to main content

paged_ring_buffer/
lockless_ring_buffer.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! This is a lockless ring buffer modeled after https://docs.kernel.org/trace/ring-buffer-design.html
6use fuchsia_rcu::RcuDroppable;
7use fuchsia_runtime::vmar_root_self;
8use fuchsia_trace;
9use shared_buffer::SharedBuffer;
10use starnix_logging::{log_error, log_info, log_warn};
11use starnix_sync::{LockDepMutex, PerfRingBufferStateLock};
12use starnix_types::PAGE_SIZE;
13use starnix_uapi::errors::Errno;
14use starnix_uapi::{errno, error, from_status_like_fdio};
15use std::mem::MaybeUninit;
16use std::sync::atomic::{AtomicUsize, Ordering};
17
18use crate::tail_val::{AtomicTailVal, TailVal};
19
20/// Callback passed to `OutputBuffer::write_each()`. Matches starnix_core's `OutputBufferCallback`.
21pub type OutputBufferCallback<'a> = dyn FnMut(&mut [MaybeUninit<u8>]) -> Result<usize, Errno> + 'a;
22
23/// The subset of starnix_core's `OutputBuffer` that `LocklessRingBuffer::read()` uses.
24pub trait OutputBuffer {
25    /// Returns the number of bytes that can still be written.
26    fn available(&self) -> usize;
27    /// Calls `callback` with each writable segment, and returns the number of bytes written.
28    fn write_each(&mut self, callback: &mut OutputBufferCallback<'_>) -> Result<usize, Errno>;
29}
30
31#[derive(RcuDroppable)]
32struct Node {
33    // page_data is the memory view for this page.
34    page_data: SharedBuffer,
35    // next and prev are pointers to the next and previous nodes.
36    // creating a linked list of pages.
37    next: AtomicUsize,
38    prev: AtomicUsize,
39    // Offset of the next free byte in `page_data`, with `WRITE_OFFSET_SEALED_BIT` set once the page
40    // is sealed. Writers claim space with a CAS in `try_reserve_on_page()`, `finalize()` seals the
41    // page, and resetting the page for reuse stores `PAGE_HEADER_SIZE`, which also unseals it.
42    //
43    // Memory ordering: every access is Relaxed. A claim and the seal are both read-modify-writes,
44    // and each read-modify-write reads the last value before it in this field's modification
45    // order, whatever the memory ordering. A plain load has no such guarantee and can return an
46    // older value.
47    //
48    // Synchronizes with: nothing.
49    //
50    // Data ordering: no other data is published through this field. Records and `data_size`
51    // reach the reader through `active_writers` (see `finalize()`). The plain loads below must
52    // still see other threads' writes to this field. Each gets a happens-before edge from another
53    // atomic, which is enough: a load cannot return a value older than one written or read by an
54    // access that happens before it (coherence).
55    //
56    // The load in `release_writer()` must see the claims of the writers that released the page
57    // before it. For example, Writer A is the last to release a page that Writer B filled:
58    //
59    //   1. Writer A claims, moving `write_offset` to `PAGE_SIZE - 8`.
60    //   2. Writer B claims, moving `write_offset` to `PAGE_SIZE`.
61    //   3. Writer B: `active_writers.fetch_sub(1, Release)`, writer count 2 -> 1.
62    //   4. Writer A: `active_writers.fetch_sub(1, Release)`, writer count 1 -> 0.
63    //   5. Writer A, now the last writer: `fence(Acquire)`.
64    //   6. Writer A: `write_offset.load(Relaxed)`, to check whether the page is full.
65    //
66    // Step 4 reads the value that step 3 wrote, and step 5 is an acquire fence after step 4, so
67    // step 3 synchronizes with step 5. Then step 2 happens before step 6, so by coherence step 6
68    // cannot return the older `PAGE_SIZE - 8`, and Writer A finalizes the full page. Therefore
69    // Relaxed is enough for the claims and the load. The seal in `finalize()` is a
70    // read-modify-write, so it needs no such edge.
71    //
72    // A writer on a new tail page must see the page's reset. If its load in
73    // `try_reserve_on_page()` returned the offset from the page's last use, the writer could
74    // treat the page as full and finalize it:
75    //
76    //   1. Thread T, in `advance_to_next_page()`, resets `write_offset` to `PAGE_HEADER_SIZE`,
77    //      or sees that another thread already reset it.
78    //   2. Thread T moves the tail: `tail_page.compare_exchange(.., AcqRel)`.
79    //   3. Writer W, in `reserve()`: `tail_page.load(Acquire)` returns the new tail.
80    //   4. Writer W, in `try_reserve_on_page()`: `write_offset.load(Relaxed)`.
81    //
82    // Step 3 reads the value that step 2 wrote, so step 2 synchronizes with step 3. Then step 1
83    // happens before step 4, so by coherence step 4 returns the reset value or a later one.
84    // Therefore Relaxed is enough for the resets and the load. `enable()` uses `ref_count` in
85    // place of `tail_page`: it resets every page before its Release store of `ref_count`, and
86    // `reserve()` increments `ref_count` with Acquire, so a writer that enters after `enable()`
87    // synchronizes with that store.
88    write_offset: AtomicUsize,
89    // Number of active writers currently reserving or writing to this page.
90    active_writers: AtomicUsize,
91}
92
93const FLAG_MASK: usize = 0b11 << 62;
94// Normal link between pages in the circular list.
95const FLAG_NORMAL: usize = 0b00 << 62;
96// Marks the link pointing to the head page (the oldest page with data).
97const FLAG_HEADER: usize = 0b01 << 62;
98// Set on the link pointing to the head page when it is being swapped by the reader.
99const FLAG_UPDATE: usize = 0b10 << 62;
100
101// The highest bit (bit 63) of `active_writers` is used as a flag indicating if the page is active.
102// When the page is finalized, this bit is cleared to 0.
103const PAGE_ACTIVE_BIT: usize = 1 << 63;
104// The second highest bit (bit 62) of `active_writers` is used to coordinate/claim finalization.
105const PAGE_FINALIZED_BIT: usize = 1 << 62;
106const FLAGS_MASK: usize = PAGE_ACTIVE_BIT | PAGE_FINALIZED_BIT;
107const ACTIVE_WRITERS_MASK: usize = !FLAGS_MASK;
108
109// Bit 63 of `write_offset` marks the page as sealed: it accepts no new reservations.
110// `Node::finalize()` sets this bit in the same read-modify-write that reads the page's final
111// offset, and `try_reserve_on_page()` rejects a sealed page. Offsets never exceed `PAGE_SIZE`, so
112// they never reach this bit.
113//
114// Ftrace closes the same window differently. The writer that does not fit overshoots `write`
115// with `local_add_return()`, and `rb_reset_tail()` pads the rest of the page and leaves `write`
116// at the end of the page, so that no later writer can slip in. Claims here are a bounded CAS that
117// never overshoots, so the page needs an explicit marker. `Node::unsealed()` plays the role of
118// ftrace's `RB_WRITE_MASK`.
119const WRITE_OFFSET_SEALED_BIT: usize = 1 << 63;
120
121// When yielding under high contention, if we busy-spin we risk priority inversion and CPU starvation
122// of preempted writers. Sleeping for 50 microseconds gives the OS scheduler sufficient window (accounting
123// for 2-5us context switch overhead and 15-30us thread execution time) to reschedule and execute the
124// preempted writer so it can release its reservation.
125const SPIN_SLEEP_DURATION: std::time::Duration = std::time::Duration::from_micros(50);
126
127// When progressive sleep is triggered (exceeding 1000 yields), we back off with a 10 milliseconds sleep.
128// This is a robust scheduling window to guarantee that Zircon schedules the preempted writer thread
129// to complete its commit/release logic.
130const PROGRESSIVE_SLEEP_DURATION: std::time::Duration = std::time::Duration::from_millis(10);
131
132impl Node {
133    /// Returns the byte offset in a raw `write_offset` value, without `WRITE_OFFSET_SEALED_BIT`.
134    fn unsealed(raw_write_offset: usize) -> usize {
135        raw_write_offset & !WRITE_OFFSET_SEALED_BIT
136    }
137
138    fn get_index(val: usize) -> usize {
139        val & !FLAG_MASK
140    }
141    fn get_flags(val: usize) -> usize {
142        val & FLAG_MASK
143    }
144    fn make_val(index: usize, flags: usize) -> usize {
145        index | flags
146    }
147
148    /// Finalizes the page by sealing `write_offset` and writing the data size to the page header.
149    fn finalize(&self) {
150        // Attempt to claim the finalization role for this page.
151        // If another thread already claimed it, return early without touching `write_offset` or
152        // `page_data`, so a losing finalizer has no side effects. A page reset clears
153        // `PAGE_FINALIZED_BIT`. See the TODO in `advance_to_next_page()`.
154        //
155        // - Acquire: pairs with the Release store of `PAGE_ACTIVE_BIT` in `advance_to_next_page()`
156        //   that recycled this page. It is meant to order this thread's `data_size` write below
157        //   after the recycle's clear of `data_size`, so that the clear cannot overwrite it.
158        //   TODO(https://fxbug.dev/570251032): The recycle clears `data_size` after that store,
159        //   so this pairing does not order the clear yet. A recycler preempted between the two
160        //   can zero a finalized page's `data_size`, and `swap_reader_page()` then returns `None`
161        //   for that page forever.
162        //   This thread needs no edge from the writers' Release `fetch_sub`: the seal below is a
163        //   read-modify-write (see `Node::write_offset`), and the reader acquires
164        //   `active_writers` itself before it reads the records.
165        // - Release: orders nothing. Nothing this thread wrote before the `fetch_or` is read
166        //   through it, and as a read-modify-write it continues the writers' release sequences
167        //   whatever its ordering. The `fetch_and` below publishes `data_size`.
168        //   TODO(https://fxbug.dev/570251032): Relax to Acquire.
169        let old_val = self.active_writers.fetch_or(PAGE_FINALIZED_BIT, Ordering::AcqRel);
170        if old_val & PAGE_FINALIZED_BIT != 0 {
171            return;
172        }
173
174        // Seal `write_offset` and read the final offset in the same read-modify-write. Every
175        // claim in `try_reserve_on_page()` that comes before this RMW in `write_offset`'s
176        // modification order is included in the offset, and every claim after it reads the
177        // sealed bit and fails. Relaxed: see `Node::write_offset`.
178        let write_offset =
179            Node::unsealed(self.write_offset.fetch_or(WRITE_OFFSET_SEALED_BIT, Ordering::Relaxed));
180
181        debug_assert!(
182            (LocklessRingBuffer::PAGE_HEADER_SIZE..=(*PAGE_SIZE) as usize).contains(&write_offset)
183        );
184        let data_size = write_offset - LocklessRingBuffer::PAGE_HEADER_SIZE;
185        self.page_data.write_at(8, &(data_size as u64).to_le_bytes());
186
187        // Clear `PAGE_ACTIVE_BIT` to publish the completed page.
188        // - Release: orders the `data_size` write above before this store. The reader's Acquire
189        //   load of `active_writers` in `swap_reader_page()` waits until `PAGE_ACTIVE_BIT` is clear
190        //   and the writer count is 0. Every later change to `active_writers` is a read-modify-write
191        //   (a writer's `fetch_sub` in `release_writer()`), so the value the reader sees belongs to
192        //   the release sequence headed by this `fetch_and`. The reader therefore synchronizes with
193        //   this `fetch_and`, and sees `data_size`, even when the last writer releases after it.
194        //   Likewise, each writer's Release `fetch_sub` heads a release sequence that the reader's
195        //   load reads from, so the reader also sees every record.
196        self.active_writers.fetch_and(!PAGE_ACTIVE_BIT, Ordering::Release);
197    }
198
199    /// Releases a writer reservation on this node, and finalizes the node if it was the last writer.
200    fn release_writer(&self) {
201        // Release: orders this writer's claim and record writes before the decrement. It pairs
202        // with the `fence(Acquire)` below in whichever writer releases the page last, so that
203        // writer's `write_offset` load sees this claim. It also pairs with the Acquire loads of
204        // `active_writers` in `swap_reader_page()`, so the reader sees the record, and in
205        // `advance_to_next_page()`, so the page is reused only after the record is written.
206        let prev_writers = self.active_writers.fetch_sub(1, Ordering::Release);
207
208        // If this was the last writer finalizing an active page (`PAGE_ACTIVE_BIT | 1`), we ensure the page is finalized.
209        // Note that if the page was already finalized by the advancing thread, `PAGE_FINALIZED_BIT` will be set,
210        // meaning `prev_writers` will have it set and we won't enter this branch, which is correct since it's already finalized.
211        if prev_writers == PAGE_ACTIVE_BIT | 1 {
212            // Acquire: pairs with the Release `fetch_sub` of every writer that released before us.
213            // Until the page is reset, only read-modify-writes change `active_writers`, so our
214            // `fetch_sub` reads from their release sequences and their claims happen before the
215            // load below. Without the fence, the load can return an older value, such as our own
216            // claim, and skip finalizing a page that another writer filled.
217            std::sync::atomic::fence(Ordering::Acquire);
218
219            // Relaxed: the fence above makes the other writers' claims happen before this load.
220            // See `Node::write_offset`. The load can also return the seal of a `finalize()` that
221            // claimed the page after our `fetch_sub`. Then the call below loses the claim and does
222            // nothing.
223            let write_offset = Node::unsealed(self.write_offset.load(Ordering::Relaxed));
224            if write_offset >= (*PAGE_SIZE) as usize {
225                // Since this thread holds a valid `Reservation`, `ref_count` remains > 0, which blocks
226                // `disable()` from completing and shrinking the VMO. This guarantees that the ring VMO
227                // remains enabled and valid for memory access.
228                // Without this check and finalization on failure, there is a liveness bug where a full page
229                // could never be finalized because the successful writer already skipped finalization.
230                self.finalize();
231            }
232        }
233    }
234}
235
236// We are only 64 bit today, but make it easy to find this assumption if we go to a smaller arch.
237static_assertions::const_assert!(std::mem::size_of::<usize>() == 8);
238
239// Use the high bit to indicate the ring is enabled. This makes the default, 0,
240// which is disabled, an easy value to reason about. And the ref count is the lower 63 bits.
241const RING_ENABLED_BIT: usize = 1 << 63;
242
243#[derive(RcuDroppable)]
244pub struct LocklessRingBuffer {
245    vmo: zx::Vmo,
246    mapping: SharedBuffer,
247    // linked list of pages that represent the ring, backed by the mapping (and the vmo).
248    nodes: Vec<Node>,
249    // The page where to read the data from the ring.
250    head_page: AtomicUsize,
251    // The page where writes add to the ring, tagged with a generation. See `TailVal`.
252    tail_page: AtomicTailVal,
253
254    // A page used by the reader of the ring.
255    reader_page: AtomicUsize,
256
257    // Tracks the global number of active readers and writers of the ring in the lower 63 bits.
258    // The highest bit (RING_ENABLED_BIT) is set when the ring is enabled.
259    ref_count: AtomicUsize,
260    // True indicates drop old pages to write new data when the ring is full. If false, writes fail until
261    // the data is read, effectively dropping new data and keeping the old data.
262    overwrite: bool,
263    // Number of dropped pages when overwrite is true.
264    dropped_pages: std::sync::atomic::AtomicU64,
265    // Used to calculate the delta from the last event. Since this is written to by multiple threads,
266    // we keep an atomic value for this.
267    prev_timestamp: std::sync::atomic::AtomicU64,
268    // The async trace event ID for write events.
269    write_event_async_id: fuchsia_trace::Id,
270    // Tracks whether a reader is currently active, used to validate that concurrent reads
271    // on a single ring buffer are not supported and do not happen. This can be removed if we
272    // are convinced it is not necessary.
273    reader_active: std::sync::atomic::AtomicBool,
274    // Mutex to serialize enable() and disable() calls to prevent racing. This should
275    // never be accessed by other methods to avoid locking during reading and writing.
276    // It holds the start time of the current session.
277    state_mutex: LockDepMutex<zx::BootInstant, PerfRingBufferStateLock>,
278}
279impl LocklessRingBuffer {
280    // This is an Ftrace page header consisting of a u64 timestamp at offset 0 and a u64 data size at offset 8.
281    pub const PAGE_HEADER_SIZE: usize = 16;
282
283    /// Creates a new LocklessRingBuffer.
284    /// size_bytes: Size of the ring buffer. Must be at least PAGE_SIZE * 3 and will be rounded up to a multiple of PAGE_SIZE.
285    /// overwrite: If true, old pages will be dropped when the ring is full. If false, writes will fail until the data is read.
286    /// write_event_async_id: The async trace event ID for write events.
287    pub fn new(
288        size_bytes: usize,
289        overwrite: bool,
290        write_event_async_id: fuchsia_trace::Id,
291    ) -> Result<Self, Errno> {
292        let requested_pages = size_bytes.div_ceil((*PAGE_SIZE) as usize);
293        // 3 pages are needed, 1 for the read page, 1 for head, and one to swap.
294        let pages = std::cmp::max(3, requested_pages);
295        let total_nodes = pages;
296        // Every node index must fit in the index field of `tail_page`.
297        if total_nodes > TailVal::MAX_NODES {
298            return error!(ENOMEM);
299        }
300        let capacity = total_nodes * (*PAGE_SIZE) as usize;
301
302        // Create VMO
303        let vmo = starnix_logging::with_zx_name(
304            zx::Vmo::create_with_opts(zx::VmoOptions::RESIZABLE, capacity as u64)
305                .map_err(|_| errno!(ENOMEM))?,
306            b"starnix:tracefs",
307        );
308        // Map VMO
309        let addr = vmar_root_self()
310            .map(0, &vmo, 0, capacity, zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE)
311            .map_err(|e| from_status_like_fdio!(e))?;
312        // SAFETY: The address returned by `vmar.map` is valid for `capacity` bytes.
313        let mapping = unsafe { SharedBuffer::new(addr as *mut u8, capacity) };
314
315        // Create nodes
316        let mut nodes = Vec::with_capacity(total_nodes);
317        let base_ptr = addr as *mut u8;
318        for i in 0..total_nodes {
319            // SAFETY: `base_ptr` points to a valid mapping of size `capacity`.
320            // `i * PAGE_SIZE` is within the bounds of this mapping since `i < total_nodes`.
321            // The memory range `[page_ptr, page_ptr + PAGE_SIZE)` is valid and mapped.
322            let page_ptr = unsafe { base_ptr.add(i * (*PAGE_SIZE) as usize) };
323
324            nodes.push(Node {
325                // SAFETY: `page_ptr` is in bounds of the mapped region and valid for `PAGE_SIZE` bytes.
326                page_data: unsafe { SharedBuffer::new(page_ptr, (*PAGE_SIZE) as usize) },
327                next: AtomicUsize::new(0),
328                prev: AtomicUsize::new(0),
329                write_offset: AtomicUsize::new(LocklessRingBuffer::PAGE_HEADER_SIZE),
330                // Initialize active_writers to PAGE_ACTIVE_BIT, with no writers, since the page is
331                // active.
332                active_writers: AtomicUsize::new(PAGE_ACTIVE_BIT),
333            });
334        }
335        // Link first `pages - 1` nodes in a circle. The last page is reserved for the reader initialization.
336        let circle_size = pages - 1;
337        for i in 0..circle_size {
338            let next_idx = (i + 1) % circle_size;
339            let prev_idx = (i + circle_size - 1) % circle_size;
340            nodes[i].next.store(Node::make_val(next_idx, FLAG_NORMAL), Ordering::Relaxed);
341            nodes[i].prev.store(Node::make_val(prev_idx, FLAG_NORMAL), Ordering::Relaxed);
342        }
343        // Initialize the reader page, mark head page (node 0).
344        nodes[circle_size - 1].next.store(Node::make_val(0, FLAG_HEADER), Ordering::Relaxed);
345        // Initialize reader page pointers to point to the head page and its predecessor
346        nodes[pages - 1].next.store(Node::make_val(0, FLAG_NORMAL), Ordering::Relaxed);
347        nodes[pages - 1]
348            .prev
349            .store(Node::make_val(circle_size - 1, FLAG_NORMAL), Ordering::Relaxed);
350        // Initialize reader page size to 0
351        nodes[pages - 1].page_data.write_at(8, &0u64.to_le_bytes());
352        let now = zx::BootInstant::get();
353        let buffer = Self {
354            vmo,
355            mapping,
356            nodes,
357            head_page: AtomicUsize::new(0),
358            tail_page: AtomicTailVal::new(TailVal::INITIAL),
359            reader_page: AtomicUsize::new(pages - 1),
360
361            ref_count: AtomicUsize::new(RING_ENABLED_BIT),
362            overwrite,
363            dropped_pages: std::sync::atomic::AtomicU64::new(0),
364            prev_timestamp: std::sync::atomic::AtomicU64::new(now.into_nanos() as u64),
365            write_event_async_id,
366            reader_active: std::sync::atomic::AtomicBool::new(false),
367            state_mutex: LockDepMutex::new(now),
368        };
369        Ok(buffer)
370    }
371    #[cfg(test)]
372    fn dropped_pages(&self) -> u64 {
373        self.dropped_pages.load(Ordering::Relaxed)
374    }
375}
376// Debugging support for detecting live locks. Consider removing once
377// the ring has baked for some time.
378#[derive(Default, Debug)]
379struct YieldTracker {
380    /// Number of times we yielded because the next page was being updated by the reader.
381    update_flag: u64,
382    /// Number of times we yielded because the page to overwrite still had active writers.
383    node_match: u64,
384    /// Number of times we yielded because we failed to lock the head page for overwrite.
385    head_lock: u64,
386}
387impl YieldTracker {
388    fn total(&self) -> u64 {
389        self.update_flag + self.node_match + self.head_lock
390    }
391
392    /// Yields or sleeps progressively based on the total retry count.
393    fn yield_or_sleep(&self) {
394        let total = self.total();
395        if total > 1000 {
396            std::thread::sleep(PROGRESSIVE_SLEEP_DURATION);
397        } else if total > 100 {
398            std::thread::sleep(SPIN_SLEEP_DURATION);
399        } else {
400            std::thread::yield_now();
401        }
402    }
403}
404
405/// Represents a reserved region of the ring buffer that is allocated, but not committed yet.
406pub struct Reservation<'a> {
407    pub offset: usize,
408    pub node_idx: usize,
409    pub size: usize,
410    buffer: &'a LocklessRingBuffer,
411    committed: bool,
412}
413
414impl<'a> Reservation<'a> {
415    pub fn write_at(&self, rel_offset: usize, data: &[u8]) {
416        assert!(rel_offset + data.len() <= self.size, "Write exceeds reservation size");
417        self.buffer.mapping.write_at(self.offset + rel_offset, data);
418    }
419
420    fn release(&mut self) {
421        if self.committed {
422            return;
423        }
424
425        let node_idx = self.node_idx;
426        let node = &self.buffer.nodes[node_idx];
427
428        node.release_writer();
429
430        self.buffer.ref_count.fetch_sub(1, Ordering::Release);
431        self.committed = true;
432    }
433}
434
435impl<'a> Drop for Reservation<'a> {
436    fn drop(&mut self) {
437        if !self.committed {
438            starnix_logging::log_warn!("LocklessRingBuffer: Reservation dropped without commit");
439        }
440        self.release();
441    }
442}
443
444impl<'a> std::fmt::Debug for Reservation<'a> {
445    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
446        f.debug_struct("Reservation")
447            .field("offset", &self.offset)
448            .field("node_idx", &self.node_idx)
449            .field("size", &self.size)
450            .field("committed", &self.committed)
451            .finish()
452    }
453}
454#[derive(Debug, PartialEq, Eq)]
455enum AdvanceResult {
456    /// The tail page has been successfully advanced or transitioned.
457    Advanced,
458    /// Failed to advance due to transient contention. The caller should yield and retry.
459    Yielded,
460    /// A hard error occurred (e.g. buffer is full and overwrite is disabled).
461    Error(Errno),
462}
463
464impl LocklessRingBuffer {
465    /// Attempts to reserve space on the current tail page.
466    ///
467    /// Returns `Ok((offset, now, delta))` if successful, or `Err(())` if the page is full or
468    /// sealed.
469    fn try_reserve_on_page(
470        &self,
471        tail_node: &Node,
472        size: usize,
473    ) -> Result<(usize, zx::BootInstant, zx::Duration<zx::BootTimeline>), ()> {
474        // Atomically reserve space on the page using a bounded CAS loop.
475        // By verifying that `WRITE_OFFSET_SEALED_BIT` is not set and that
476        // `current_offset + size <= PAGE_SIZE` before executing the CAS, we guarantee that failed
477        // reservations never advance `write_offset` and that no reservation can succeed after
478        // `Node::finalize()` seals the page. A CAS that loses to the seal returns the sealed value,
479        // so the next iteration fails without retrying.
480        // Relaxed: see `Node::write_offset`.
481        let mut current_offset = tail_node.write_offset.load(Ordering::Relaxed);
482        loop {
483            if (current_offset & WRITE_OFFSET_SEALED_BIT) != 0
484                || current_offset + size > (*PAGE_SIZE) as usize
485            {
486                return Err(());
487            }
488            match tail_node.write_offset.compare_exchange_weak(
489                current_offset,
490                current_offset + size,
491                Ordering::Relaxed,
492                Ordering::Relaxed,
493            ) {
494                Ok(_) => break,
495                Err(actual) => current_offset = actual,
496            }
497        }
498
499        let now_candidate = zx::BootInstant::get().into_nanos() as u64;
500        let actual_prev = self.prev_timestamp.fetch_max(now_candidate, Ordering::AcqRel);
501        let final_now_nanos = std::cmp::max(now_candidate, actual_prev);
502        let now = zx::BootInstant::from_nanos(final_now_nanos as i64);
503
504        // First write on this page. Set timestamp.
505        let delta_nanos = if current_offset == LocklessRingBuffer::PAGE_HEADER_SIZE {
506            tail_node.page_data.write_at(0, &final_now_nanos.to_le_bytes());
507            0
508        } else {
509            final_now_nanos.saturating_sub(actual_prev)
510        };
511
512        let delta = zx::Duration::from_nanos(delta_nanos as i64);
513
514        Ok((current_offset, now, delta))
515    }
516
517    /// Advances the tail page to the next page in the ring.
518    ///
519    /// Handles marking the current page as full, moving the head page if overwrite is enabled,
520    /// and updating the global `tail_page` pointer.
521    fn advance_to_next_page(
522        &self,
523        tail_node: &Node,
524        tail_val: TailVal,
525        yield_tracker: &mut YieldTracker,
526    ) -> AdvanceResult {
527        // Short-circuit if another thread has already advanced the tail page past tail_val.
528        if self.tail_page.load(Ordering::Acquire) != tail_val {
529            return AdvanceResult::Advanced;
530        }
531
532        // Finalize the tail page: seal it against further reservations and publish its size.
533        // Writers that already hold reservations on it may still be writing. The reader waits
534        // for them through `active_writers`.
535        //
536        // TODO(https://fxbug.dev/570251032): A thread preempted between the `tail_page` check
537        // above and this call can make the call after `tail_node` has been reset, either as a
538        // later tail here or as a returned reader page in `swap_reader_page()`. The reset clears
539        // `PAGE_FINALIZED_BIT`, so this call wins the claim and closes the page. If the page is
540        // already the tail, it is closed live, possibly with `data_size == 0`. Once that page is
541        // the head, `swap_reader_page()` returns `None` forever, so the reader stalls and,
542        // without overwrite, writers fail with `ENOSPC`. If the tail has not reached the page
543        // yet, the page is sealed while empty, and the reset below reopens it when the tail
544        // arrives. Below, the same thread can also find `FLAG_HEADER` on `tail_node.next` and
545        // drop the head page, or fail with `ENOSPC`, while the ring still has free pages.
546        if self.is_enabled() {
547            tail_node.finalize();
548        }
549
550        let next_val = tail_node.next.load(Ordering::Acquire);
551        let next_idx = Node::get_index(next_val);
552        let next_flags = Node::get_flags(next_val);
553        if next_flags == FLAG_UPDATE {
554            // Reader is swapping this page. Wait.
555            starnix_logging::log_warn!(
556                "Reservation yielding due to FLAG_UPDATE on node {}",
557                next_idx
558            );
559            yield_tracker.update_flag += 1;
560            yield_tracker.yield_or_sleep();
561            return AdvanceResult::Yielded;
562        } else if next_flags == FLAG_HEADER {
563            if self.overwrite {
564                // Check if any active writer is on the page we want to overwrite.
565                if (self.nodes[next_idx].active_writers.load(Ordering::Acquire)
566                    & ACTIVE_WRITERS_MASK)
567                    > 0
568                {
569                    yield_tracker.node_match += 1;
570                    yield_tracker.yield_or_sleep();
571                    return AdvanceResult::Yielded;
572                }
573
574                starnix_logging::log_warn!(
575                    "LocklessRingBuffer Overwriting page {} (overwriting={})",
576                    next_idx,
577                    self.overwrite
578                );
579                // Try to lock the head page
580                let expected_next = Node::make_val(next_idx, FLAG_HEADER);
581                let locked_next = Node::make_val(next_idx, FLAG_UPDATE);
582                // AcqRel on success:
583                // - Acquire: Ensures we see the up-to-date links of the head page we are about to move.
584                // - Release: Ensures our transition to FLAG_UPDATE (locking) is visible to others.
585                // Relaxed on failure: We fail to acquire the lock and just retry.
586                if tail_node
587                    .next
588                    .compare_exchange_weak(
589                        expected_next,
590                        locked_next,
591                        Ordering::AcqRel,
592                        Ordering::Relaxed,
593                    )
594                    .is_ok()
595                {
596                    // Move the head page.
597                    let head_node = &self.nodes[next_idx];
598                    let head_next_val = head_node.next.load(Ordering::Acquire);
599                    let head_next_idx = Node::get_index(head_next_val);
600
601                    // Set FLAG_HEADER on the new head pointer
602                    let new_head_next = Node::make_val(head_next_idx, FLAG_HEADER);
603                    head_node.next.store(new_head_next, Ordering::Release);
604
605                    // Update global head_page
606                    self.head_page.store(head_next_idx, Ordering::Release);
607
608                    // Unlock the old head pointer (tail_node.next) and make it FLAG_NORMAL
609                    let unlocked_next = Node::make_val(next_idx, FLAG_NORMAL);
610                    tail_node.next.store(unlocked_next, Ordering::Release);
611                    self.dropped_pages.fetch_add(1, Ordering::Relaxed);
612                    fuchsia_trace::async_instant!(
613                        self.write_event_async_id,
614                        "starnix:trace_meta",
615                        "page dropped"
616                    );
617                    return AdvanceResult::Advanced;
618                } else {
619                    // Failed to lock head_page. Retry.
620                    yield_tracker.head_lock += 1;
621                    yield_tracker.yield_or_sleep();
622                    return AdvanceResult::Yielded;
623                }
624            } else {
625                // Buffer is full and overwrite is false.
626                starnix_logging::log_error!("LocklessRingBuffer is full");
627                return AdvanceResult::Error(errno!(ENOSPC));
628            }
629        }
630
631        let next_node = &self.nodes[next_idx];
632        // Only the thread whose CAS moves `next_node.write_offset` to `PAGE_HEADER_SIZE` (from a
633        // used or sealed offset; the CAS also clears `WRITE_OFFSET_SEALED_BIT`) resets the rest
634        // of the page. Any thread that still sees `tail_val` in `tail_page` then helps move the
635        // tail. A page sealed while empty holds `PAGE_HEADER_SIZE | WRITE_OFFSET_SEALED_BIT`,
636        // not `PAGE_HEADER_SIZE`, so it is reset here too, which reopens it.
637        let (should_advance, won_reset) = {
638            // Relaxed: see `Node::write_offset`.
639            let mut current = next_node.write_offset.load(Ordering::Relaxed);
640            loop {
641                // Another thread advanced the tail page.
642                if self.tail_page.load(Ordering::Acquire) != tail_val {
643                    break (false, false);
644                }
645                // If `current` is exactly `PAGE_HEADER_SIZE` (unsealed), the page was already
646                // reset: another thread won the reset race here, or `swap_reader_page()` or
647                // `enable()` reset it. Since `self.tail_page` hasn't advanced yet, we must still
648                // return `should_advance = true` to cooperate in advancing the global `tail_page`
649                // pointer, while returning `won_reset = false` to avoid clobbering page metadata.
650                if current == LocklessRingBuffer::PAGE_HEADER_SIZE {
651                    break (true, false);
652                }
653                match next_node.write_offset.compare_exchange_weak(
654                    current,
655                    LocklessRingBuffer::PAGE_HEADER_SIZE,
656                    Ordering::Relaxed,
657                    Ordering::Relaxed,
658                ) {
659                    Ok(_) => break (true, true),
660                    Err(actual) => current = actual,
661                }
662            }
663        };
664
665        if should_advance {
666            if won_reset {
667                // Reset `active_writers` to PAGE_ACTIVE_BIT (1) since the page is active.
668                next_node.active_writers.store(PAGE_ACTIVE_BIT, Ordering::Release);
669                // Clear the size field in the page header to avoid reader seeing stale size.
670                next_node.page_data.write_at(8, &0u64.to_le_bytes());
671            }
672
673            // Move the tail to `next_idx` in the next generation. If the CAS fails, another
674            // thread has already moved it.
675            // - Release: a writer whose `tail_page.load(Acquire)` in `reserve()` reads this value
676            //   synchronizes with this CAS. It therefore sees the reset of `write_offset` (see
677            //   `Node::write_offset`) and, if this thread won the reset, the `active_writers`
678            //   store and the header write above.
679            // - Acquire: orders nothing. This function reads nothing after the CAS, and
680            //   `reserve()` loads `tail_page` again with Acquire before it uses the new tail.
681            //   TODO(https://fxbug.dev/570251032): Relax to Release.
682            //
683            // TODO(https://fxbug.dev/570251032): A thread that did not win the reset publishes
684            // neither the `active_writers` store nor the header write, because it never read
685            // them. If the winner was preempted after its `write_offset` CAS, it has not even
686            // made them yet. A writer that reads the page's old `active_writers` value, with
687            // `PAGE_ACTIVE_BIT` clear, skips the page and moves the tail past it. When the
688            // winner's store lands, the page is active again behind the tail, and nothing
689            // finalizes it. If the writer instead finalizes the page after that store, it closes
690            // the page with `data_size == 0`. Either way, once the page is the head,
691            // `swap_reader_page()` returns `None` forever: without overwrite the reader stalls and
692            // writers fail with `ENOSPC`; with overwrite the head drop eventually discards the
693            // page.
694            if let Err(err) = self.tail_page.compare_exchange(
695                tail_val,
696                tail_val.next(next_idx),
697                Ordering::AcqRel,
698                Ordering::Relaxed,
699            ) {
700                starnix_logging::log_debug!(
701                    "Tail page already advanced by another thread: {err:?}"
702                );
703            }
704        }
705        AdvanceResult::Advanced
706    }
707
708    pub fn reserve(
709        &self,
710        size: usize,
711    ) -> Result<(Reservation<'_>, zx::BootInstant, zx::Duration<zx::BootTimeline>), Errno> {
712        // Check that the reservation is non-zero and fits within a page.
713        if size == 0 || size > (*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE {
714            return error!(EINVAL);
715        }
716        // Increment ref_count if enabled.
717        let mut val = self.ref_count.load(Ordering::Acquire);
718        loop {
719            if val & RING_ENABLED_BIT == 0 {
720                return error!(ENOMEM);
721            }
722            // Acquire on success:
723            // - Acquire: Synchronizes with the Release store in `enable()`. This guarantees that if the writer
724            //   observes that the ring is enabled, it will also observe all prior state initialization and memory
725            //   setups made by `enable()` before starting any writes.
726            // - Release: Synchronizes with `disable()`'s Acquire load on `ref_count`, ensuring that if `disable()`
727            //   observes a zero active writer count, it is guaranteed that this writer has either not yet entered
728            //   or has fully completed its operations.
729            // Note: Only the decrement in `release()` needs to be Release to ensure that the data payload writes
730            // are fully visible. Since the writer has not yet written any payload data to the page, there are no
731            // memory writes that need to be published to other threads via Release ordering at this point.
732            match self.ref_count.compare_exchange_weak(
733                val,
734                val + 1,
735                Ordering::Acquire,
736                Ordering::Relaxed,
737            ) {
738                Ok(_) => break,
739                Err(actual) => val = actual,
740            }
741        }
742
743        // Create a scope guard to decrement ref_count on error, panic, or early exit.
744        let ref_count_guard = scopeguard::guard(&self.ref_count, |ref_count| {
745            ref_count.fetch_sub(1, Ordering::Release);
746        });
747
748        // Lock the reservation.
749        let mut yield_tracker = YieldTracker::default();
750        let result = loop {
751            if !self.is_enabled() {
752                log_info!("LocklessRingBuffer: canceling reserve because ring is disabled.");
753                break error!(ENOMEM);
754            }
755            let tail_val = self.tail_page.load(Ordering::Acquire);
756            let tail_idx = tail_val.index() as usize;
757            let tail_node = &self.nodes[tail_idx];
758
759            // Increment the writer count before trying to reserve space, if it fails decrement it.
760            // We use compare_exchange_weak in a loop to ensure we only increment if the PAGE_ACTIVE_BIT is still set.
761            // Note: The success and failure orderings are Relaxed because the writer has not yet written any payload
762            // data to the page. Therefore, there are no memory writes that need to be published to other threads
763            // via Release ordering.
764            let mut active_incremented = false;
765            let mut old_writers = tail_node.active_writers.load(Ordering::Acquire);
766            loop {
767                if old_writers & PAGE_ACTIVE_BIT == 0 {
768                    break;
769                }
770                match tail_node.active_writers.compare_exchange_weak(
771                    old_writers,
772                    old_writers + 1,
773                    Ordering::Relaxed,
774                    Ordering::Relaxed,
775                ) {
776                    Ok(_) => {
777                        active_incremented = true;
778                        break;
779                    }
780                    Err(actual) => old_writers = actual,
781                }
782            }
783
784            let reservation_result = if active_incremented {
785                self.try_reserve_on_page(tail_node, size)
786            } else {
787                Err(())
788            };
789
790            // Try to claim space on the current page.
791            match reservation_result {
792                Ok((offset, now, delta)) => {
793                    break Ok((
794                        Reservation {
795                            offset: tail_idx * (*PAGE_SIZE) as usize + offset,
796                            node_idx: tail_idx,
797                            size,
798                            buffer: self,
799                            committed: false,
800                        },
801                        now,
802                        delta,
803                    ));
804                }
805                Err(()) => {
806                    // Handle the race where the last successful writer decrements active_writers but
807                    // sees prev_writers > 1 because another concurrent writer (this thread) has already
808                    // incremented it. This thread fails reservation because the page is full, decrements
809                    // active_writers to 0, and gets prev_writers == 1. We must check and finalize here to
810                    // guarantee that full pages are always finalized even if the finalizer thread failed its reserve.
811                    if active_incremented {
812                        self.nodes[tail_idx].release_writer();
813                    }
814                    // Page is full. Advance to the next page.
815                    match self.advance_to_next_page(tail_node, tail_val, &mut yield_tracker) {
816                        AdvanceResult::Advanced | AdvanceResult::Yielded => {}
817                        AdvanceResult::Error(e) => {
818                            break Err(e);
819                        }
820                    }
821                }
822            }
823            // Debug logging for high yield counts.
824            if yield_tracker.total() % 100_000_000 == 0 && yield_tracker.total() > 0 {
825                log_warn!(
826                    "LocklessRingBuffer: spinning in reservation loop.  details = {yield_tracker:?}",
827                );
828            }
829        };
830        // Cleanup and return
831        let total_yields = yield_tracker.total();
832        if total_yields > 500_000 {
833            starnix_logging::log_info!(
834                "Reservation completed with yields: total={}, details={:?}",
835                total_yields,
836                yield_tracker
837            );
838        }
839        match result {
840            Ok((res, now, delta)) => {
841                // Success: Disarm the scope guard so ref_count is NOT decremented.
842                scopeguard::ScopeGuard::into_inner(ref_count_guard);
843                Ok((res, now, delta))
844            }
845            Err(e) => {
846                // Error: The scope guard will automatically decrement ref_count when dropped.
847                Err(e)
848            }
849        }
850    }
851    pub fn commit(&self, mut reservation: Reservation<'_>) {
852        reservation.release();
853    }
854
855    fn swap_reader_page(&self) -> Option<usize> {
856        let mut retries = 0;
857        loop {
858            let head_val = self.head_page.load(Ordering::Acquire);
859            let head_idx = Node::get_index(head_val);
860            let tail_val = self.tail_page.load(Ordering::Acquire);
861            let tail_idx = tail_val.index() as usize;
862            if head_idx == tail_idx {
863                // Ring is empty or we are writing to the only page.
864                return None;
865            }
866            let head_node = &self.nodes[head_idx];
867            // Perform a single Acquire load on `active_writers` to establish happens-before with the finalizing writer.
868            // We check concurrently that the page is finalized (PAGE_ACTIVE_BIT is cleared to 0) and that there are no active writers (count is 0).
869            let active_writers = head_node.active_writers.load(Ordering::Acquire);
870            // We can swap the page if it is no longer active and has no active writers.
871            // We ignore the PAGE_FINALIZED_BIT when determining if the page is ready.
872            if (active_writers & (PAGE_ACTIVE_BIT | ACTIVE_WRITERS_MASK)) != 0 {
873                return None;
874            }
875            let mut size_bytes = [0u8; 8];
876            head_node.page_data.read_at(8, &mut size_bytes);
877            let size = u64::from_le_bytes(size_bytes);
878            if size == 0 {
879                // This can happen even if `head_page != tail_page` (the ring is not empty).
880                // Specifically, if a writer advanced the tail to the next page, the global
881                // `head_page` pointer may have advanced, but the active writer on this new
882                // `head_page` has not yet called `commit()` or finalized the page.
883                // Thus, the data size field at offset 8 in the page header remains 0.
884                return None;
885            }
886            let next_val = head_node.next.load(Ordering::Acquire);
887            let next_idx = Node::get_index(next_val);
888            let prev_val = head_node.prev.load(Ordering::Acquire);
889            let prev_idx = Node::get_index(prev_val);
890            let prev_node = &self.nodes[prev_idx];
891            // Try to lock the head page by setting UPDATE flag on prev.next
892            let expected_next = Node::make_val(head_idx, FLAG_HEADER);
893            let locked_next = Node::make_val(head_idx, FLAG_UPDATE);
894            if prev_node
895                .next
896                .compare_exchange_weak(
897                    expected_next,
898                    locked_next,
899                    Ordering::AcqRel,
900                    Ordering::Relaxed,
901                )
902                .is_ok()
903            {
904                // Locked! Now perform the swap.
905                let reader_idx = self.reader_page.load(Ordering::Acquire);
906                let next_node = &self.nodes[next_idx];
907                // Update pointers to insert reader node.
908                self.nodes[reader_idx]
909                    .prev
910                    .store(Node::make_val(prev_idx, FLAG_NORMAL), Ordering::Relaxed);
911                self.nodes[reader_idx]
912                    .next
913                    .store(Node::make_val(next_idx, FLAG_HEADER), Ordering::Relaxed);
914                next_node.prev.store(Node::make_val(reader_idx, FLAG_NORMAL), Ordering::Relaxed);
915                // Reset write offset of the new head page (old reader page)
916                self.nodes[reader_idx]
917                    .write_offset
918                    .store(LocklessRingBuffer::PAGE_HEADER_SIZE, Ordering::Relaxed);
919                // Reset `active_writers` to PAGE_ACTIVE_BIT before recycling.
920                self.nodes[reader_idx].active_writers.store(PAGE_ACTIVE_BIT, Ordering::Relaxed);
921                // Clear the size field in the page header to avoid reader seeing stale size.
922                self.nodes[reader_idx].page_data.write_at(8, &0u64.to_le_bytes());
923                // Unlock prev_node and set its next to reader_idx as NORMAL
924                let unlocked_val = Node::make_val(reader_idx, FLAG_NORMAL);
925                prev_node.next.store(unlocked_val, Ordering::Release);
926                // Update global pointers
927                self.head_page.store(next_idx, Ordering::Release);
928                self.reader_page.store(head_idx, Ordering::Release);
929                // Return the old head page index (now reader page)
930                return Some(head_idx);
931            }
932            // Failed to lock, retry.
933            starnix_logging::log_warn!("swap_reader_page failed to lock, yielding");
934            retries += 1;
935            if retries >= 100_000 {
936                starnix_logging::log_error!("LocklessRingBuffer: HUNG in swap_reader_page loop");
937                return None;
938            }
939            // Failed to acquire the lock for swapping. Spin and retry.
940            // Note: The log message above says "yielding" but we actually spin here
941            // to avoid context switch overhead for this short wait.
942            std::hint::spin_loop();
943        }
944    }
945    pub fn read(&self, buf: &mut dyn OutputBuffer) -> Result<usize, Errno> {
946        // Validate that concurrent reads on a single ring buffer do not happen.
947        // By design in both ftrace/tracefs and Starnix VFS, only a single reader is active at a time.
948        if self.reader_active.swap(true, Ordering::AcqRel) {
949            starnix_logging::log_error!(
950                "LocklessRingBuffer: concurrent reads detected! Concurrent reads are not supported by design."
951            );
952            return error!(EBUSY);
953        }
954
955        let _lock_guard = scopeguard::guard(&self.reader_active, |reader_active| {
956            reader_active.store(false, Ordering::Release);
957        });
958
959        // Increment ref_count if enabled to prevent disable() from shrinking the VMO
960        // while we are reading from it. We use a compare_exchange loop instead of
961        // fetch_add to avoid modifying the counter if the ring is already disabled,
962        // which prevents live-locking disable().
963        let mut val = self.ref_count.load(Ordering::Acquire);
964        loop {
965            if val & RING_ENABLED_BIT == 0 {
966                return error!(EAGAIN);
967            }
968            match self.ref_count.compare_exchange_weak(
969                val,
970                val + 1,
971                Ordering::Acquire,
972                Ordering::Relaxed,
973            ) {
974                Ok(_) => break,
975                Err(actual) => val = actual,
976            }
977        }
978
979        let _guard = scopeguard::guard(&self.ref_count, |ref_count| {
980            ref_count.fetch_sub(1, Ordering::Release);
981        });
982
983        if buf.available() < (*PAGE_SIZE) as usize {
984            starnix_logging::log_error!(
985                "Buffer is too small: {} bytes (needs {} bytes)",
986                buf.available(),
987                (*PAGE_SIZE) as usize
988            );
989            return error!(EINVAL);
990        }
991        // TODO(https://fxbug.dev/505532201): Consider handling buffers larger than a page.
992        // this currently only works for a single page.
993        if let Some(idx) = self.swap_reader_page() {
994            let node = &self.nodes[idx];
995            let mut offset = 0;
996            let page_size = (*PAGE_SIZE) as usize;
997            let bytes_written = buf.write_each(&mut move |segment| {
998                let available = page_size - offset;
999                if available == 0 {
1000                    return Ok(0);
1001                }
1002                let size = std::cmp::min(segment.len(), available);
1003                // SAFETY: We are writing to this segment, so it's safe to treat it as initialized after we write.
1004                // We cast it to `&mut [u8]` to pass to `read_at`.
1005                let segment_mut_u8 = unsafe {
1006                    std::slice::from_raw_parts_mut(segment.as_mut_ptr() as *mut u8, size)
1007                };
1008                node.page_data.read_at(offset, segment_mut_u8);
1009                offset += size;
1010                Ok(size)
1011            })?;
1012            return Ok(bytes_written);
1013        }
1014        error!(EAGAIN)
1015    }
1016
1017    pub fn size_bytes(&self) -> usize {
1018        self.nodes.len() * (*PAGE_SIZE) as usize
1019    }
1020
1021    pub fn is_enabled(&self) -> bool {
1022        self.ref_count.load(Ordering::Acquire) & RING_ENABLED_BIT != 0
1023    }
1024    pub fn disable(&self) -> Result<u64, Errno> {
1025        let _lock = self.state_mutex.lock();
1026        // Clear the enabled bit.
1027        self.ref_count.fetch_and(!RING_ENABLED_BIT, Ordering::AcqRel);
1028        let mut yield_count: u64 = 0;
1029        loop {
1030            let active_writers = self.ref_count.load(Ordering::Acquire);
1031            if active_writers == 0 {
1032                break;
1033            }
1034            yield_count += 1;
1035            if yield_count % 1_000_000 == 0 {
1036                log_error!(
1037                    "LocklessRingBuffer: disable is waiting for {active_writers} active writers",
1038                );
1039            }
1040            // We use yield_now() here as a compromise:
1041            // - spin_wait would waste too much CPU if the writer is descheduled.
1042            // - sleep adds too much latency for what should be a very short wait (copying data).
1043            // Yielding gives other threads a chance to run while keeping latency low.
1044            std::thread::yield_now();
1045        }
1046        if yield_count > 500_000 {
1047            log_warn!("LocklessRingBuffer disable took {} yields", yield_count);
1048        }
1049        if let Err(e) = self.vmo.set_size(0) {
1050            starnix_logging::log_error!(
1051                "LocklessRingBuffer disable failed to set_size(0): {:?}",
1052                e
1053            );
1054            return Err(from_status_like_fdio!(e));
1055        }
1056        let dropped = self.dropped_pages.load(Ordering::Relaxed);
1057        Ok(dropped)
1058    }
1059
1060    /// If the ring is disabled, resets it to empty, enables it, and returns the time of the reset.
1061    ///
1062    /// If the ring is already enabled, does nothing, so in-flight writers and readers, and the
1063    /// `tail_page` generation, are unaffected. In that case it returns the time of the last reset,
1064    /// by `new()` or by an earlier `enable()`.
1065    pub fn enable(&self) -> Result<zx::BootInstant, Errno> {
1066        let mut last_reset_guard = self.state_mutex.lock();
1067        // Only `enable()` and `disable()` change `RING_ENABLED_BIT` once the ring is shared, and
1068        // both hold `state_mutex`, so the ring stays enabled until we return. The mutex, not the
1069        // Acquire load in `is_enabled()`, is what makes this check reliable.
1070        if self.is_enabled() {
1071            return Ok(*last_reset_guard);
1072        }
1073        // The ring is disabled, so no reader or writer holds a reference, and none can take one
1074        // until the Release store of `ref_count` at the end of this function:
1075        // 1. `disable()` clears `RING_ENABLED_BIT` and drains `ref_count` to 0, holding
1076        //    `state_mutex`.
1077        // 2. `reserve()` and `read()` take a reference only with a CAS that fails while
1078        //    `RING_ENABLED_BIT` is clear, and decrement only a reference they took.
1079        // So the resets below, including the non-atomic page writes, race with nothing. A thread
1080        // that takes a reference afterwards synchronizes with that Release store and sees them.
1081        // Relaxed: `state_mutex` orders this load after the drain in `disable()`.
1082        debug_assert_eq!(self.ref_count.load(Ordering::Relaxed), 0);
1083        let initial_pages = self.nodes.len() - 1;
1084        let capacity = self.nodes.len() * (*PAGE_SIZE) as usize;
1085        if let Err(e) = self.vmo.set_size(capacity as u64) {
1086            starnix_logging::log_error!("LocklessRingBuffer enable failed to set_size: {:?}", e);
1087            return Err(from_status_like_fdio!(e));
1088        }
1089        let now = zx::BootInstant::get();
1090        // Reset state.
1091        self.head_page.store(0, Ordering::Release);
1092        // Move the tail to node 0 in the next generation rather than back to generation 0. Today
1093        // no `tail_page` sample survives into the new ring: every thread that loads `tail_page`
1094        // holds a `ref_count` reference, and `disable()` drained them. The bump keeps a stale
1095        // sample from matching the new tail if a later change loads `tail_page` without a
1096        // reference.
1097        // Relaxed load: `new()` and earlier `enable()` calls happen before this call, by
1098        // construction and through `state_mutex`. Every other `tail_page` write was made by a
1099        // thread that held a reference and released it with a Release decrement. The only plain
1100        // store to `ref_count` is in `enable()`, before any reference of its epoch is taken, so
1101        // every later write to it is a read-modify-write. The Acquire load in `disable()` that
1102        // saw 0 therefore reads from the release sequence of each decrement of that epoch, and
1103        // `state_mutex` orders `disable()` before this call, so the load returns the latest
1104        // value.
1105        // Relaxed store: the Release store of `ref_count` at the end of this function publishes
1106        // it.
1107        self.tail_page.store(self.tail_page.load(Ordering::Relaxed).next(0), Ordering::Relaxed);
1108
1109        self.reader_page.store(initial_pages, Ordering::Release);
1110
1111        self.dropped_pages.store(0, Ordering::Release);
1112        self.prev_timestamp.store(now.into_nanos() as u64, Ordering::Release);
1113        *last_reset_guard = now;
1114
1115        for i in 0..self.nodes.len() {
1116            // Relaxed: see `Node::write_offset`.
1117            self.nodes[i]
1118                .write_offset
1119                .store(LocklessRingBuffer::PAGE_HEADER_SIZE, Ordering::Relaxed);
1120            self.nodes[i].active_writers.store(PAGE_ACTIVE_BIT, Ordering::Release);
1121        }
1122        for i in 0..initial_pages {
1123            let next_idx = (i + 1) % initial_pages;
1124            let prev_idx = (i + initial_pages - 1) % initial_pages;
1125            self.nodes[i].next.store(Node::make_val(next_idx, FLAG_NORMAL), Ordering::Relaxed);
1126            self.nodes[i].prev.store(Node::make_val(prev_idx, FLAG_NORMAL), Ordering::Relaxed);
1127        }
1128        self.nodes[initial_pages - 1].next.store(Node::make_val(0, FLAG_HEADER), Ordering::Relaxed);
1129
1130        // Initialize reader page size to 0
1131        self.nodes[initial_pages].page_data.write_at(8, &0u64.to_le_bytes());
1132        // Initialize first page.
1133        let _ = self.vmo.op_range(zx::VmoOp::ZERO, 0, *PAGE_SIZE);
1134        let nanos = now.into_nanos() as u64;
1135        self.nodes[0].page_data.write_at(0, &nanos.to_le_bytes());
1136
1137        // Set enabled bit LAST to ensure state is fully reset before writes start.
1138        self.ref_count.store(RING_ENABLED_BIT, Ordering::Release);
1139        Ok(now)
1140    }
1141}
1142impl Drop for LocklessRingBuffer {
1143    fn drop(&mut self) {
1144        let (ptr, len) = self.mapping.as_ptr_len();
1145        // SAFETY: The mapping was created in `new` and is valid for this lifetime.
1146        // We are freeing it now because the object is being destroyed.
1147        unsafe {
1148            let _ = vmar_root_self().unmap(ptr as usize, len);
1149        }
1150    }
1151}
1152
1153#[cfg(test)]
1154mod tests {
1155    use super::*;
1156    use std::sync::Arc;
1157
1158    /// An `OutputBuffer` that collects what is written into a `Vec`, in place of starnix_core's
1159    /// `VecOutputBuffer`.
1160    struct VecOutputBuffer {
1161        data: Vec<u8>,
1162        capacity: usize,
1163    }
1164    impl VecOutputBuffer {
1165        fn new(capacity: usize) -> Self {
1166            Self { data: Vec::with_capacity(capacity), capacity }
1167        }
1168        fn data(&self) -> &[u8] {
1169            &self.data
1170        }
1171        fn reset(&mut self) {
1172            self.data.clear();
1173        }
1174    }
1175    impl OutputBuffer for VecOutputBuffer {
1176        fn available(&self) -> usize {
1177            self.capacity - self.data.len()
1178        }
1179        fn write_each(&mut self, callback: &mut OutputBufferCallback<'_>) -> Result<usize, Errno> {
1180            let mut segment = vec![MaybeUninit::new(0u8); self.available()];
1181            let written = callback(&mut segment)?;
1182            // SAFETY: `segment` was fully initialized above.
1183            self.data.extend(segment[..written].iter().map(|b| unsafe { b.assume_init() }));
1184            Ok(written)
1185        }
1186    }
1187    #[repr(C)]
1188    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
1189    struct TestMessage {
1190        thread_index: u32,
1191        timestamp_nanos: u64,
1192        delta: u64,
1193        data: [u8; 12],
1194    }
1195    /// Returns the `data_size` field of the page header at the start of `page`.
1196    fn read_data_size(page: &[u8]) -> usize {
1197        const DATA_SIZE_OFFSET: usize = 8;
1198        assert!(page.len() >= LocklessRingBuffer::PAGE_HEADER_SIZE, "page shorter than header");
1199        let mut size_bytes = [0u8; 8];
1200        size_bytes.copy_from_slice(&page[DATA_SIZE_OFFSET..LocklessRingBuffer::PAGE_HEADER_SIZE]);
1201        u64::from_le_bytes(size_bytes) as usize
1202    }
1203
1204    impl TestMessage {
1205        pub const SIZE: usize = 32;
1206        fn to_bytes(&self) -> [u8; TestMessage::SIZE] {
1207            let mut bytes = [0u8; TestMessage::SIZE];
1208            bytes[0..4].copy_from_slice(&self.thread_index.to_le_bytes());
1209            bytes[4..12].copy_from_slice(&self.timestamp_nanos.to_le_bytes());
1210            bytes[12..20].copy_from_slice(&self.delta.to_le_bytes());
1211            bytes[20..32].copy_from_slice(&self.data);
1212            bytes
1213        }
1214        fn from_bytes(bytes: &[u8]) -> Self {
1215            let mut thread_index = [0u8; 4];
1216            thread_index.copy_from_slice(&bytes[0..4]);
1217            let mut timestamp_nanos = [0u8; 8];
1218            timestamp_nanos.copy_from_slice(&bytes[4..12]);
1219            let mut delta = [0u8; 8];
1220            delta.copy_from_slice(&bytes[12..20]);
1221            let mut data = [0u8; 12];
1222            data.copy_from_slice(&bytes[20..32]);
1223            Self {
1224                thread_index: u32::from_le_bytes(thread_index),
1225                timestamp_nanos: u64::from_le_bytes(timestamp_nanos),
1226                delta: u64::from_le_bytes(delta),
1227                data,
1228            }
1229        }
1230    }
1231    #[test]
1232    fn test_init() {
1233        let buffer =
1234            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1235                .unwrap();
1236        assert_eq!(buffer.size_bytes(), 3 * (*PAGE_SIZE) as usize);
1237    }
1238    #[test]
1239    fn test_reserve() {
1240        let buffer =
1241            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1242                .unwrap();
1243        let res = buffer.reserve(100).unwrap();
1244        assert_eq!(res.0.size, 100);
1245        assert_eq!(res.0.offset, LocklessRingBuffer::PAGE_HEADER_SIZE);
1246        assert_eq!(res.0.node_idx, 0);
1247    }
1248    #[test]
1249    fn test_commit() {
1250        let buffer =
1251            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1252                .unwrap();
1253        let res = buffer.reserve(100).unwrap();
1254        buffer.commit(res.0);
1255        assert_eq!(buffer.nodes[0].active_writers.load(Ordering::Relaxed), PAGE_ACTIVE_BIT);
1256    }
1257    #[test]
1258    fn test_swap_reader_page_empty() {
1259        let buffer =
1260            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1261                .unwrap();
1262        assert_eq!(buffer.swap_reader_page(), None);
1263    }
1264    #[test]
1265    fn test_swap_reader_page_success() {
1266        let buffer =
1267            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1268                .unwrap();
1269        // Fill node 0
1270        let res1 =
1271            buffer.reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE).unwrap();
1272        buffer.commit(res1.0);
1273        // Next reserve should go to node 1
1274        let res2 = buffer.reserve(100).unwrap();
1275        assert_eq!(res2.0.node_idx, 1);
1276        buffer.commit(res2.0);
1277        // Page 0 has size > 0 and tail advanced to Page 1.
1278        // So swap_reader_page should succeed!
1279        let old_head = buffer.swap_reader_page();
1280        assert_eq!(old_head, Some(0));
1281        // And head_page should now have advanced to index 1.
1282        assert_eq!(buffer.head_page.load(Ordering::Relaxed), 1);
1283    }
1284    #[test]
1285    fn test_concurrent_reserve() {
1286        use std::sync::Arc;
1287        let buffer = Arc::new(
1288            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1289                .unwrap(),
1290        );
1291        let mut handles = vec![];
1292        for _ in 0..5 {
1293            let buffer_clone = Arc::clone(&buffer);
1294            let handle = std::thread::spawn(move || {
1295                for _ in 0..20 {
1296                    if let Ok(res) = buffer_clone.reserve(10) {
1297                        buffer_clone.commit(res.0);
1298                    }
1299                }
1300            });
1301            handles.push(handle);
1302        }
1303        for handle in handles {
1304            handle.join().unwrap();
1305        }
1306        for i in 0..buffer.nodes.len() {
1307            assert_eq!(buffer.nodes[i].active_writers.load(Ordering::Relaxed), PAGE_ACTIVE_BIT);
1308        }
1309    }
1310    #[test]
1311    fn test_reserve_moves_to_next_page() {
1312        let buffer =
1313            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1314                .unwrap();
1315        // Reserve most of the page, leaving only 50 bytes
1316        let res1 = buffer
1317            .reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE - 50)
1318            .unwrap();
1319        buffer.commit(res1.0);
1320        // Try to reserve 100 bytes. It shouldn't fit on node 0.
1321        let res2 = buffer.reserve(100).unwrap();
1322        // It should be on node 1.
1323        assert_eq!(res2.0.node_idx, 1);
1324        assert_eq!(res2.0.offset, (*PAGE_SIZE) as usize + LocklessRingBuffer::PAGE_HEADER_SIZE);
1325    }
1326    #[test]
1327    fn test_reserve_overwrite() {
1328        let buffer =
1329            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
1330                .unwrap();
1331        // Fill node 0 and commit
1332        let res1 =
1333            buffer.reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE).unwrap();
1334        buffer.commit(res1.0);
1335        // Fill node 1 but do NOT commit
1336        let _res2 =
1337            buffer.reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE).unwrap();
1338        // Now tail is at node 1. Next reserve wants to move to node 0.
1339        // But commit is still at node 0.
1340        // So it should trigger overwrite.
1341        let res3 = buffer.reserve(100).unwrap();
1342        // It should succeed and be on node 0.
1343        assert_eq!(res3.0.node_idx, 0);
1344        assert_eq!(buffer.dropped_pages(), 1);
1345        // And head_page should have advanced to 1.
1346        assert_eq!(buffer.head_page.load(Ordering::Relaxed), 1);
1347    }
1348    #[test]
1349    fn test_read() {
1350        let buffer =
1351            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1352                .unwrap();
1353        let res1 =
1354            buffer.reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE).unwrap();
1355        let data = vec![1u8; (*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE];
1356        res1.0.write_at(0, &data);
1357        buffer.commit(res1.0);
1358        // Advance commit page to node 1
1359        let res2 = buffer.reserve(100).unwrap();
1360        buffer.commit(res2.0);
1361        let mut dest = VecOutputBuffer::new((*PAGE_SIZE) as usize);
1362        let result = buffer.read(&mut dest);
1363        assert!(result.is_ok());
1364        assert_eq!(result.unwrap(), (*PAGE_SIZE) as usize);
1365        assert_eq!(&dest.data()[LocklessRingBuffer::PAGE_HEADER_SIZE..], &data[..]);
1366    }
1367    #[test]
1368    fn test_enable_disable() {
1369        let buffer =
1370            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1371                .unwrap();
1372        assert!(buffer.disable().is_ok());
1373        assert_eq!(buffer.ref_count.load(Ordering::Relaxed) & RING_ENABLED_BIT, 0);
1374        let res = buffer.reserve(100);
1375        assert_eq!(res.unwrap_err(), starnix_uapi::errno!(ENOMEM));
1376        assert!(buffer.enable().is_ok());
1377        assert_eq!(buffer.size_bytes(), 3 * (*PAGE_SIZE) as usize);
1378        let res = buffer.reserve(100);
1379        assert!(res.is_ok());
1380    }
1381
1382    // Mirrors how starnix_core's TraceEventQueue uses the ring: create it in overwrite mode with a
1383    // page-multiple size larger than the minimum, disable it, then enable it.
1384    #[test]
1385    fn test_enable_after_disable_restores_requested_size() {
1386        let size_bytes = 512 * (*PAGE_SIZE) as usize;
1387        let buffer = LocklessRingBuffer::new(size_bytes, true, fuchsia_trace::Id::new()).unwrap();
1388        assert_eq!(buffer.size_bytes(), size_bytes);
1389
1390        buffer.disable().unwrap();
1391        assert_eq!(buffer.vmo.get_size().unwrap(), 0);
1392
1393        buffer.enable().unwrap();
1394        assert_eq!(buffer.size_bytes(), size_bytes);
1395        assert_eq!(buffer.vmo.get_size().unwrap(), size_bytes as u64);
1396    }
1397    // Spawns a reader thread that periodically drains the lockless ring buffer.
1398    fn start_reader_thread(
1399        buffer_reader: Arc<LocklessRingBuffer>,
1400        writers_done_reader: Arc<std::sync::atomic::AtomicBool>,
1401    ) -> std::thread::JoinHandle<Vec<TestMessage>> {
1402        start_gated_reader_thread(buffer_reader, writers_done_reader, false)
1403    }
1404
1405    // Spawns a reader thread that optionally yields until at least one page has been
1406    // dropped (overwritten) by writers before draining.
1407    fn start_gated_reader_thread(
1408        buffer_reader: Arc<LocklessRingBuffer>,
1409        writers_done_reader: Arc<std::sync::atomic::AtomicBool>,
1410        wait_for_dropped_pages: bool,
1411    ) -> std::thread::JoinHandle<Vec<TestMessage>> {
1412        std::thread::spawn(move || {
1413            if wait_for_dropped_pages {
1414                while buffer_reader.dropped_pages() == 0
1415                    && !writers_done_reader.load(Ordering::Acquire)
1416                {
1417                    std::thread::yield_now();
1418                }
1419            }
1420            let mut all_messages = Vec::new();
1421            let mut dest = VecOutputBuffer::new((*PAGE_SIZE) as usize);
1422            let mut consecutive_eagain = 0;
1423            loop {
1424                dest.reset();
1425                match buffer_reader.read(&mut dest) {
1426                    Ok(bytes_read) => {
1427                        consecutive_eagain = 0;
1428                        assert_eq!(bytes_read, (*PAGE_SIZE) as usize);
1429                        let mut header_ts_bytes = [0u8; 8];
1430                        header_ts_bytes.copy_from_slice(&dest.data()[0..8]);
1431                        let header_timestamp = u64::from_le_bytes(header_ts_bytes);
1432                        let data_size = read_data_size(dest.data());
1433                        let max_offset = LocklessRingBuffer::PAGE_HEADER_SIZE + data_size;
1434                        let mut offset = LocklessRingBuffer::PAGE_HEADER_SIZE;
1435                        let mut first_msg = true;
1436                        while offset + TestMessage::SIZE <= max_offset {
1437                            let msg_bytes = &dest.data()[offset..offset + TestMessage::SIZE];
1438                            let msg = TestMessage::from_bytes(msg_bytes);
1439                            if first_msg {
1440                                if header_timestamp != msg.timestamp_nanos {
1441                                    println!(
1442                                        "HEADER TIMESTAMP MISMATCH: header={}, msg={}",
1443                                        header_timestamp, msg.timestamp_nanos
1444                                    );
1445                                }
1446                                first_msg = false;
1447                            }
1448                            all_messages.push(msg);
1449                            offset += TestMessage::SIZE;
1450                        }
1451                    }
1452                    Err(e) if e == starnix_uapi::errno!(EAGAIN) => {
1453                        if writers_done_reader.load(Ordering::Acquire) {
1454                            let head_val = buffer_reader.head_page.load(Ordering::Relaxed);
1455                            let tail_val = buffer_reader.tail_page.load(Ordering::Relaxed);
1456                            // If the writers are done and the reader has caught up to the tail page
1457                            // index, all readable pages have been drained, so we break the loop.
1458                            if Node::get_index(head_val) == tail_val.index() as usize {
1459                                break;
1460                            }
1461                            consecutive_eagain += 1;
1462                            assert!(
1463                                consecutive_eagain < 500,
1464                                "LocklessRingBuffer reader stuck: consecutive EAGAIN limit exceeded (5s) after writers finished. head_page={}, tail_page={}, reader_page={}",
1465                                Node::get_index(head_val),
1466                                tail_val.index(),
1467                                buffer_reader.reader_page.load(Ordering::Relaxed)
1468                            );
1469                        }
1470                        std::thread::sleep(std::time::Duration::from_millis(10));
1471                    }
1472                    Err(e) => panic!("Unexpected error from read: {:?}", e),
1473                }
1474            }
1475            all_messages
1476        })
1477    }
1478    fn check_all_message_data(all_messages: &[TestMessage], num_threads: u32) {
1479        let mut prev_timestamp = 0;
1480        let mut thread_counts = vec![0; num_threads as usize];
1481        let mut out_of_order = 0;
1482        let mut corrupted = 0;
1483        for msg in all_messages {
1484            if msg.timestamp_nanos < prev_timestamp {
1485                println!("OUT OF ORDER: prev_timestamp={}, current={:?}", prev_timestamp, msg);
1486                out_of_order += 1;
1487            }
1488            prev_timestamp = msg.timestamp_nanos;
1489            if &msg.data != b"Event data\0\0" || msg.thread_index >= num_threads {
1490                println!("CORRUPTED: msg={:?}", msg);
1491                corrupted += 1;
1492            } else {
1493                thread_counts[msg.thread_index as usize] += 1;
1494            }
1495        }
1496        println!(
1497            "TEST_RESULT: Read {} messages, {} out of order, {} corrupted. Thread counts: {:?}",
1498            all_messages.len(),
1499            out_of_order,
1500            corrupted,
1501            thread_counts
1502        );
1503        // We do not assert out_of_order is 0 because wait-free reservation can cause slight reordering of timestamps.
1504        assert_eq!(corrupted, 0, "Found corrupted messages");
1505    }
1506    #[test]
1507    fn test_concurrent_read_write_4() {
1508        let num_threads = 4;
1509        let msgs_per_thread = 64;
1510        // 4 threads * 64 msgs * 32 bytes = 8192 bytes.
1511        // Each page can hold (4096 - 16) / 32 = 127 msgs (with 16 bytes unused at the end).
1512        // 256 msgs will take 2 full pages (254 msgs) + 2 msgs on a 3rd page.
1513        let buffer = Arc::new(
1514            LocklessRingBuffer::new(4 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1515                .unwrap(),
1516        );
1517        let writers_done = Arc::new(std::sync::atomic::AtomicBool::new(false));
1518        let mut handles = vec![];
1519        // Spawn reader
1520        let buffer_reader = Arc::clone(&buffer);
1521        let writers_done_reader = Arc::clone(&writers_done);
1522        let reader_handle = start_reader_thread(buffer_reader, writers_done_reader);
1523        // Spawn writers
1524        for thread_index in 0..num_threads {
1525            let buffer_clone = Arc::clone(&buffer);
1526            let handle = std::thread::spawn(move || {
1527                std::thread::sleep(std::time::Duration::from_millis(10 + thread_index as u64));
1528                for _ in 0..msgs_per_thread {
1529                    // Reserve exactly TestMessage::SIZE bytes
1530                    let (res, now, delta) = buffer_clone.reserve(TestMessage::SIZE).unwrap();
1531                    let msg = TestMessage {
1532                        thread_index,
1533                        timestamp_nanos: now.into_nanos() as u64,
1534                        delta: delta.into_nanos() as u64,
1535                        data: *b"Event data\0\0",
1536                    };
1537                    res.write_at(0, &msg.to_bytes());
1538                    buffer_clone.commit(res);
1539                    std::thread::sleep(std::time::Duration::from_nanos(thread_index as u64));
1540                }
1541            });
1542            handles.push(handle);
1543        }
1544        for handle in handles {
1545            handle.join().unwrap();
1546        }
1547        writers_done.store(true, Ordering::Release);
1548        let all_messages = reader_handle.join().unwrap();
1549        // Check the messages
1550        check_all_message_data(&all_messages, num_threads);
1551        // Trailing messages might remain on the final unfinalized tail page. We expect between 250 and 256 messages to be successfully read.
1552        assert!(
1553            all_messages.len() >= 250 && all_messages.len() <= 256,
1554            "Expected between 250 and 256 messages, got {}",
1555            all_messages.len()
1556        );
1557    }
1558    #[test]
1559    fn test_concurrent_read_write_1_thread() {
1560        let num_threads = 1;
1561        let msgs_per_thread = 256;
1562        // 1 thread * 256 msgs = 256 msgs.
1563        // Each page holds 127 msgs. 256 msgs requires 3 data pages.
1564        // We use 5 pages total to provide enough capacity.
1565        let buffer = Arc::new(
1566            LocklessRingBuffer::new(5 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
1567                .unwrap(),
1568        );
1569        let writers_done = Arc::new(std::sync::atomic::AtomicBool::new(false));
1570        let mut handles = vec![];
1571        // Spawn reader
1572        let buffer_reader = Arc::clone(&buffer);
1573        let writers_done_reader = Arc::clone(&writers_done);
1574        let reader_handle = start_reader_thread(buffer_reader, writers_done_reader);
1575        // Spawn writers
1576        for thread_index in 0..num_threads {
1577            let buffer_clone = Arc::clone(&buffer);
1578            let handle = std::thread::spawn(move || {
1579                std::thread::sleep(std::time::Duration::from_millis(10 + thread_index as u64));
1580                for _ in 0..msgs_per_thread {
1581                    let (res, now, delta) = buffer_clone.reserve(TestMessage::SIZE).unwrap();
1582                    let msg = TestMessage {
1583                        thread_index,
1584                        timestamp_nanos: now.into_nanos() as u64,
1585                        delta: delta.into_nanos() as u64,
1586                        data: *b"Event data\0\0",
1587                    };
1588                    res.write_at(0, &msg.to_bytes());
1589                    buffer_clone.commit(res);
1590                    std::thread::sleep(std::time::Duration::from_nanos(thread_index as u64));
1591                }
1592            });
1593            handles.push(handle);
1594        }
1595        for handle in handles {
1596            handle.join().unwrap();
1597        }
1598        writers_done.store(true, Ordering::Release);
1599        let all_messages = reader_handle.join().unwrap();
1600        check_all_message_data(&all_messages, num_threads);
1601        assert_eq!(
1602            all_messages.len(),
1603            254,
1604            "Expected exactly 254 messages, got {}",
1605            all_messages.len()
1606        );
1607    }
1608    #[test]
1609    fn test_concurrent_read_write_8_threads() {
1610        let num_threads = 8;
1611        let msgs_per_thread = 128;
1612        // 4 threads * 64 msgs * 32 bytes = 8192 bytes.
1613        // 8 threads * 128 msgs = 1024 msgs.
1614        // Each page holds 127 msgs. 1024 msgs requires 9 data pages.
1615        // We use 12 pages total to provide enough capacity so no pages are overwritten.
1616        let buffer = Arc::new(
1617            LocklessRingBuffer::new(12 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1618                .unwrap(),
1619        );
1620        let writers_done = Arc::new(std::sync::atomic::AtomicBool::new(false));
1621        let _writers_done_guard = scopeguard::guard(Arc::clone(&writers_done), |done| {
1622            done.store(true, Ordering::Release);
1623        });
1624        let mut handles = vec![];
1625        // Spawn reader
1626        let buffer_reader = Arc::clone(&buffer);
1627        let writers_done_reader = Arc::clone(&writers_done);
1628        let reader_handle = start_reader_thread(buffer_reader, writers_done_reader);
1629        // Spawn writers
1630        for thread_index in 0..num_threads {
1631            let buffer_clone = Arc::clone(&buffer);
1632            let handle = std::thread::spawn(move || {
1633                std::thread::sleep(std::time::Duration::from_millis(10 + thread_index as u64));
1634                for _ in 0..msgs_per_thread {
1635                    // Reserve exactly TestMessage::SIZE bytes
1636                    let mut retries = 0;
1637                    let (res, now, delta) = loop {
1638                        match buffer_clone.reserve(TestMessage::SIZE) {
1639                            Ok(r) => break r,
1640                            Err(e) if e == starnix_uapi::errno!(ENOSPC) => {
1641                                retries += 1;
1642                                assert!(
1643                                    retries < 100,
1644                                    "LocklessRingBuffer: ENOSPC transient limit exceeded (100ms). Reader may have hung."
1645                                );
1646                                // Under high contention, writers can transiently catch up to the head page.
1647                                // This is expected and will clear up once the reader thread gets scheduled
1648                                // and swaps a page.
1649                                std::thread::sleep(std::time::Duration::from_millis(1));
1650                            }
1651                            Err(e) => panic!("Unexpected error: {:?}", e),
1652                        }
1653                    };
1654                    let msg = TestMessage {
1655                        thread_index,
1656                        timestamp_nanos: now.into_nanos() as u64,
1657                        delta: delta.into_nanos() as u64,
1658                        data: *b"Event data\0\0",
1659                    };
1660                    res.write_at(0, &msg.to_bytes());
1661                    buffer_clone.commit(res);
1662                    std::thread::sleep(std::time::Duration::from_nanos(thread_index as u64));
1663                }
1664            });
1665            handles.push(handle);
1666        }
1667        for handle in handles {
1668            handle.join().unwrap();
1669        }
1670
1671        writers_done.store(true, Ordering::Release);
1672        let all_messages = reader_handle.join().unwrap();
1673        // Check the messages
1674        check_all_message_data(&all_messages, num_threads);
1675        // The last page in the circle (current tail page) cannot be swapped by the reader
1676        // (to prevent reading uncommitted/partial data). The number of messages left sitting on
1677        // the tail page when the writers finish depends entirely on concurrent thread scheduling
1678        // variance under high contention. We expect between 1008 and 1024 messages to be read
1679        // (leaving 0 to 16 messages unread on the final tail page).
1680        assert!(
1681            all_messages.len() >= 1008 && all_messages.len() <= 1024,
1682            "Expected between 1008 and 1024 messages, got {}",
1683            all_messages.len()
1684        );
1685    }
1686    #[test]
1687    fn test_disable_waits_for_ref_count() {
1688        use std::sync::Arc;
1689        use std::sync::atomic::{AtomicBool, Ordering};
1690        let buffer = Arc::new(
1691            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
1692                .unwrap(),
1693        );
1694        // Thread 1: Reserves space, keeping ref_count = 1 (plus enabled bit).
1695        let res = buffer.reserve(100).unwrap();
1696        let disable_finished = Arc::new(AtomicBool::new(false));
1697        let disable_finished_clone = Arc::clone(&disable_finished);
1698        let buffer_clone = Arc::clone(&buffer);
1699        // Thread 2: Calls disable. This should block because ref_count has active writers.
1700        let handle = std::thread::spawn(move || {
1701            buffer_clone.disable().unwrap();
1702            disable_finished_clone.store(true, Ordering::Relaxed);
1703        });
1704        // Wait for Thread 2 to start and set size_bytes to 0
1705        while buffer.ref_count.load(Ordering::Acquire) & RING_ENABLED_BIT != 0 {
1706            std::hint::spin_loop();
1707        }
1708        // Verify that disable has NOT finished
1709        assert!(!disable_finished.load(Ordering::Relaxed));
1710        // Verify that new reserves fail with ENOMEM because disable() cleared RING_ENABLED_BIT
1711        let res2 = buffer.reserve(100);
1712        assert_eq!(res2.unwrap_err(), starnix_uapi::errno!(ENOMEM));
1713        // Now write data and commit the first reservation.
1714        // If the VMO had been shrunk, write_data would panic/fault here.
1715        let data = vec![42u8; 100];
1716        res.0.write_at(0, &data);
1717        buffer.commit(res.0);
1718        // Wait for Thread 2 to finish
1719        handle.join().unwrap();
1720        // Verify disable finished
1721        assert!(disable_finished.load(Ordering::Relaxed));
1722    }
1723    #[test]
1724    fn test_reservation_drop_cancels_writer_count() {
1725        let buffer =
1726            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
1727                .unwrap();
1728        // 1. Reserve space. ref_count becomes 1 (plus enabled bit).
1729        let res = buffer.reserve(100).unwrap();
1730        assert_eq!(buffer.ref_count.load(Ordering::Relaxed), RING_ENABLED_BIT | 1);
1731        // 2. Drop the reservation without commit.
1732        std::mem::drop(res.0);
1733        // 3. Verify ref_count becomes 0 (plus enabled bit).
1734        assert_eq!(buffer.ref_count.load(Ordering::Relaxed), RING_ENABLED_BIT);
1735        // 4. Verify that a new reservation can be committed (proving the dropped one didn't block).
1736        let res2 = buffer.reserve(100).unwrap();
1737        buffer.commit(res2.0);
1738    }
1739    #[test]
1740    fn test_overwrite_and_read_4_pages() {
1741        let num_threads = 1;
1742        // Use 6 pages total (5 data pages) to allow exactly 4 readable pages
1743        // (1 page is the active commit page).
1744        let buffer = Arc::new(
1745            LocklessRingBuffer::new(6 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
1746                .unwrap(),
1747        );
1748        let writers_done = Arc::new(std::sync::atomic::AtomicBool::new(false));
1749        // 5 data pages capacity + 2 overwritten pages = 7 pages to write.
1750        // Each page holds exactly 127 messages.
1751        let msgs_per_thread = 7 * 127;
1752        let buffer_clone = Arc::clone(&buffer);
1753        let thread_index = 0;
1754        let writer_handle = std::thread::spawn(move || {
1755            for _ in 0..msgs_per_thread {
1756                let (res, now, delta) = buffer_clone.reserve(TestMessage::SIZE).unwrap();
1757                let msg = TestMessage {
1758                    thread_index,
1759                    timestamp_nanos: now.into_nanos() as u64,
1760                    delta: delta.into_nanos() as u64,
1761                    data: *b"Event data\0\0",
1762                };
1763                res.write_at(0, &msg.to_bytes());
1764                buffer_clone.commit(res);
1765            }
1766        });
1767        writer_handle.join().unwrap();
1768        assert_eq!(buffer.dropped_pages(), 2, "Expected 2 pages to be dropped");
1769        writers_done.store(true, Ordering::Release);
1770        let buffer_reader = Arc::clone(&buffer);
1771        let writers_done_reader = Arc::clone(&writers_done);
1772        let reader_handle = start_reader_thread(buffer_reader, writers_done_reader);
1773        let all_messages = reader_handle.join().unwrap();
1774        check_all_message_data(&all_messages, num_threads);
1775        assert_eq!(all_messages.len(), 4 * 127, "Expected exactly 4 pages of messages");
1776    }
1777    #[test]
1778    fn test_reserve_full_producer_consumer() {
1779        let buffer =
1780            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1781                .unwrap();
1782        // Fill node 0
1783        let res1 =
1784            buffer.reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE).unwrap();
1785        buffer.commit(res1.0);
1786        // Fill node 1
1787        let res2 =
1788            buffer.reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE).unwrap();
1789        buffer.commit(res2.0);
1790        // Buffer is full (2 data pages used, head at 0, commit at 1 but next is head).
1791        // Next reserve should fail with ENOSPC because overwrite is false.
1792        let res3 = buffer.reserve(100);
1793        assert_eq!(res3.unwrap_err(), starnix_uapi::errno!(ENOSPC));
1794    }
1795
1796    #[test]
1797    fn test_reserve_zero_size() {
1798        let buffer =
1799            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1800                .unwrap();
1801        let res = buffer.reserve(0);
1802        assert_eq!(res.unwrap_err(), starnix_uapi::errno!(EINVAL));
1803    }
1804    #[test]
1805    fn test_concurrent_overwrite_stability() {
1806        let num_threads = 4;
1807        let msgs_per_thread = 256;
1808        // Small buffer to force overwrites
1809        let buffer = Arc::new(
1810            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
1811                .unwrap(),
1812        );
1813        // Synchronously fill the active circular buffer (2 data pages) to capacity.
1814        // The reader thread is gated below so it does not drain the buffer until
1815        // the writer threads wrap around to Node 0 and trigger a dropped page.
1816        let max_payload = (*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE;
1817        let num_msgs = max_payload / TestMessage::SIZE;
1818        for _ in 0..(2 * num_msgs) {
1819            let (res, now, delta) = buffer.reserve(TestMessage::SIZE).unwrap();
1820            let msg = TestMessage {
1821                thread_index: 0,
1822                timestamp_nanos: now.into_nanos() as u64,
1823                delta: delta.into_nanos() as u64,
1824                data: *b"Event data\0\0",
1825            };
1826            res.write_at(0, &msg.to_bytes());
1827            buffer.commit(res);
1828        }
1829
1830        let writers_done = Arc::new(std::sync::atomic::AtomicBool::new(false));
1831        let mut handles = vec![];
1832
1833        let buffer_reader = Arc::clone(&buffer);
1834        let writers_done_reader = Arc::clone(&writers_done);
1835        // Gate reader until writers wrap around and trigger an overwrite (dropped page),
1836        // guaranteeing overwrite stability is exercised without relying on timing or sleeps.
1837        let reader_handle = start_gated_reader_thread(buffer_reader, writers_done_reader, true);
1838
1839        // Spawn writers
1840        for thread_index in 0..num_threads {
1841            let buffer_clone = Arc::clone(&buffer);
1842            let handle = std::thread::spawn(move || {
1843                for _ in 0..msgs_per_thread {
1844                    if let Ok((res, now, delta)) = buffer_clone.reserve(TestMessage::SIZE) {
1845                        let msg = TestMessage {
1846                            thread_index,
1847                            timestamp_nanos: now.into_nanos() as u64,
1848                            delta: delta.into_nanos() as u64,
1849                            data: *b"Event data\0\0",
1850                        };
1851                        res.write_at(0, &msg.to_bytes());
1852                        buffer_clone.commit(res);
1853                    }
1854                    // Yield to allow other threads to run and cause contention
1855                    std::thread::yield_now();
1856                }
1857            });
1858            handles.push(handle);
1859        }
1860        for handle in handles {
1861            handle.join().unwrap();
1862        }
1863        writers_done.store(true, Ordering::Release);
1864        let all_messages = reader_handle.join().unwrap();
1865        // Check that data is not corrupted
1866        check_all_message_data(&all_messages, num_threads);
1867        // Verify that some pages were dropped (overwritten)
1868        assert!(buffer.dropped_pages() > 0, "Expected at least some dropped pages");
1869    }
1870
1871    #[test]
1872    fn test_out_of_order_completion_same_page() {
1873        let buffer =
1874            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1875                .unwrap();
1876
1877        let header_size = LocklessRingBuffer::PAGE_HEADER_SIZE;
1878        let page_size = (*PAGE_SIZE) as usize;
1879        let available_space = page_size - header_size;
1880
1881        let size1 = available_space / 2;
1882        let size2 = available_space - size1;
1883
1884        // Reserve first chunk
1885        let (res1, _, _) = buffer.reserve(size1).unwrap();
1886        // Reserve second chunk (fills the page)
1887        let (res2, _, _) = buffer.reserve(size2).unwrap();
1888
1889        // Commit second chunk first (out of order)
1890        buffer.commit(res2);
1891
1892        // Page should not be finalized yet because res1 is still active
1893        assert!(buffer.swap_reader_page().is_none());
1894
1895        // Commit first chunk
1896        buffer.commit(res1);
1897
1898        // Force advance tail by making a reservation that won't fit on Page 0.
1899        let _ = buffer.reserve(10).unwrap();
1900
1901        // Now page should be finalized
1902        let swapped = buffer.swap_reader_page();
1903        assert!(swapped.is_some());
1904        assert_eq!(swapped.unwrap(), 0); // First data page is node 0
1905    }
1906
1907    #[test]
1908    fn test_concurrent_readers_rejection() {
1909        #[derive(Debug)]
1910        struct SleepingOutputBuffer {
1911            barrier_in_available: Arc<std::sync::Barrier>,
1912            barrier_test_done: Arc<std::sync::Barrier>,
1913        }
1914        impl OutputBuffer for SleepingOutputBuffer {
1915            fn write_each(
1916                &mut self,
1917                _callback: &mut OutputBufferCallback<'_>,
1918            ) -> Result<usize, Errno> {
1919                Ok(0)
1920            }
1921            fn available(&self) -> usize {
1922                // Signal the main thread that we are active and holding the read lock, and block
1923                // until the main thread reaches its wait point.
1924                self.barrier_in_available.wait();
1925
1926                // Block here until the main thread has completed its concurrent read assertion.
1927                self.barrier_test_done.wait();
1928
1929                (*PAGE_SIZE) as usize
1930            }
1931        }
1932
1933        let buffer = Arc::new(
1934            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1935                .unwrap(),
1936        );
1937
1938        let barrier_in_available = Arc::new(std::sync::Barrier::new(2));
1939        let barrier_test_done = Arc::new(std::sync::Barrier::new(2));
1940
1941        let buffer_clone = Arc::clone(&buffer);
1942        let barrier_in_available_clone = Arc::clone(&barrier_in_available);
1943        let barrier_test_done_clone = Arc::clone(&barrier_test_done);
1944        let handle = std::thread::spawn(move || {
1945            let mut dest = SleepingOutputBuffer {
1946                barrier_in_available: barrier_in_available_clone,
1947                barrier_test_done: barrier_test_done_clone,
1948            };
1949            let _ = buffer_clone.read(&mut dest);
1950        });
1951
1952        // Wait until the background thread enters `available()` and is holding the read lock.
1953        barrier_in_available.wait();
1954
1955        // Perform concurrent read while background thread holds `reader_active` lock.
1956        let mut dest = VecOutputBuffer::new((*PAGE_SIZE) as usize);
1957        let res = buffer.read(&mut dest);
1958        assert_eq!(res.unwrap_err(), starnix_uapi::errno!(EBUSY));
1959
1960        // Signal the background thread that the assertion is done and it can proceed.
1961        barrier_test_done.wait();
1962
1963        handle.join().unwrap();
1964    }
1965
1966    #[test]
1967    fn test_stale_offset_livelock() {
1968        let num_threads = 8;
1969        let msgs_per_thread = 200;
1970        // Small 3-page buffer to force wraparounds and high contention
1971        let buffer = Arc::new(
1972            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
1973                .unwrap(),
1974        );
1975        let mut handles = vec![];
1976
1977        for thread_index in 0..num_threads {
1978            let buffer_clone = Arc::clone(&buffer);
1979            let handle = std::thread::spawn(move || {
1980                for _ in 0..msgs_per_thread {
1981                    if let Ok((res, now, delta)) = buffer_clone.reserve(TestMessage::SIZE) {
1982                        let msg = TestMessage {
1983                            thread_index,
1984                            timestamp_nanos: now.into_nanos() as u64,
1985                            delta: delta.into_nanos() as u64,
1986                            data: *b"Event data\0\0",
1987                        };
1988                        res.write_at(0, &msg.to_bytes());
1989                        buffer_clone.commit(res);
1990                    }
1991                    std::thread::yield_now();
1992                }
1993            });
1994            handles.push(handle);
1995        }
1996
1997        for handle in handles {
1998            handle.join().unwrap();
1999        }
2000
2001        let dropped = buffer.dropped_pages();
2002        println!("High contention completed. Total dropped pages = {}", dropped);
2003        // Dropped pages should be reasonable and not spike to infinity or cause deadlock
2004        assert!(dropped > 0);
2005    }
2006
2007    #[test]
2008    fn test_writer_preemption_and_overwrite_prevention() {
2009        let buffer = Arc::new(
2010            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2011                .unwrap(),
2012        );
2013
2014        // 1. Thread 1 reserves space on Node 0, but delays committing (preempted)
2015        let res1 = buffer.reserve(100).unwrap();
2016        assert_eq!(res1.0.node_idx, 0);
2017
2018        // 2. Thread 2 fills the rest of Node 0, Node 1, and wraps around to Node 0.
2019        // Thread 2 should block or spin since Node 0 has active writers (active_writers > 0)
2020        let buffer_clone = Arc::clone(&buffer);
2021        let writer_finished = Arc::new(std::sync::atomic::AtomicBool::new(false));
2022        let writer_finished_clone = Arc::clone(&writer_finished);
2023
2024        let handle = std::thread::spawn(move || {
2025            // Fill Node 0
2026            let res2 = buffer_clone
2027                .reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE - 100)
2028                .unwrap();
2029            buffer_clone.commit(res2.0);
2030
2031            // Fill Node 1
2032            let res3 = buffer_clone
2033                .reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE)
2034                .unwrap();
2035            buffer_clone.commit(res3.0);
2036
2037            // Try to reserve again - wraps around to Node 0 (which still has res1 active)
2038            // This should block/spin until we commit res1.
2039            let res4 = buffer_clone.reserve(100).unwrap();
2040            buffer_clone.commit(res4.0);
2041
2042            writer_finished_clone.store(true, Ordering::Release);
2043        });
2044
2045        // Give the thread some time to reach the block point
2046        std::thread::sleep(std::time::Duration::from_millis(50));
2047        assert!(!writer_finished.load(Ordering::Acquire));
2048
2049        // Now commit res1, which unblocks the wraparound writer
2050        buffer.commit(res1.0);
2051
2052        handle.join().unwrap();
2053        assert!(writer_finished.load(Ordering::Acquire));
2054    }
2055
2056    #[test]
2057    fn test_extreme_disable_enable_stress() {
2058        let num_threads = 8;
2059        let msgs_per_thread = 100;
2060        let buffer = Arc::new(
2061            LocklessRingBuffer::new(5 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2062                .unwrap(),
2063        );
2064        let mut handles = vec![];
2065
2066        // 1. Spawn 8 writer threads writing continuously until they successfully write msgs_per_thread.
2067        for thread_index in 0..num_threads {
2068            let buffer_clone = Arc::clone(&buffer);
2069            let handle = std::thread::spawn(move || {
2070                let mut count = 0;
2071                while count < msgs_per_thread {
2072                    match buffer_clone.reserve(TestMessage::SIZE) {
2073                        Ok((res, now, delta)) => {
2074                            let msg = TestMessage {
2075                                thread_index,
2076                                timestamp_nanos: now.into_nanos() as u64,
2077                                delta: delta.into_nanos() as u64,
2078                                data: *b"Event data\0\0",
2079                            };
2080                            res.write_at(0, &msg.to_bytes());
2081                            buffer_clone.commit(res);
2082                            count += 1;
2083                        }
2084                        Err(e) if e == starnix_uapi::errno!(ENOMEM) => {
2085                            // Expected when disabled. Yield and retry.
2086                            std::thread::yield_now();
2087                        }
2088                        Err(e) => panic!("Unexpected error during reserve: {:?}", e),
2089                    }
2090                }
2091                count
2092            });
2093            handles.push(handle);
2094        }
2095
2096        // 2. Coordinator thread repeatedly disables and enables the ring buffer.
2097        let buffer_clone = Arc::clone(&buffer);
2098        let coordinator = std::thread::spawn(move || {
2099            for _ in 0..20 {
2100                std::thread::sleep(std::time::Duration::from_millis(5));
2101                // Disable the ring buffer. This must successfully wait for all active writers.
2102                let _dropped = buffer_clone.disable().unwrap();
2103
2104                // Ensure subsequent reservations fail while disabled.
2105                let res = buffer_clone.reserve(TestMessage::SIZE);
2106                assert_eq!(res.unwrap_err(), starnix_uapi::errno!(ENOMEM));
2107
2108                std::thread::sleep(std::time::Duration::from_millis(2));
2109                // Re-enable the ring buffer.
2110                let _now = buffer_clone.enable().unwrap();
2111            }
2112        });
2113
2114        coordinator.join().unwrap();
2115        let mut total_writes = 0;
2116        for handle in handles {
2117            total_writes += handle.join().unwrap();
2118        }
2119        println!("Disable/enable stress completed. Total writes = {}", total_writes);
2120        assert_eq!(total_writes, num_threads * msgs_per_thread);
2121    }
2122
2123    #[test]
2124    fn test_reader_loop_swapping_high_contention() {
2125        let num_threads = 8usize;
2126        let msgs_per_thread = 100;
2127        // 8 threads * 100 msgs * 32 bytes = 25600 bytes.
2128        // Each page holds 127 messages.
2129        // Let's make a 10-page buffer to have ample space.
2130        let buffer = Arc::new(
2131            LocklessRingBuffer::new(10 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2132                .unwrap(),
2133        );
2134        let writers_done = Arc::new(std::sync::atomic::AtomicBool::new(false));
2135        let mut handles = vec![];
2136
2137        // Spawn unified reader thread that loops read() draining all pages.
2138        let buffer_reader = Arc::clone(&buffer);
2139        let writers_done_reader = Arc::clone(&writers_done);
2140        let reader_handle = start_reader_thread(buffer_reader, writers_done_reader);
2141
2142        // Spawn 8 writer threads.
2143        for thread_index in 0..num_threads {
2144            let buffer_clone = Arc::clone(&buffer);
2145            let handle = std::thread::spawn(move || {
2146                for _ in 0..msgs_per_thread {
2147                    let (res, now, delta) = buffer_clone.reserve(TestMessage::SIZE).unwrap();
2148                    let msg = TestMessage {
2149                        thread_index: thread_index as u32,
2150                        timestamp_nanos: now.into_nanos() as u64,
2151                        delta: delta.into_nanos() as u64,
2152                        data: *b"Event data\0\0",
2153                    };
2154                    res.write_at(0, &msg.to_bytes());
2155                    buffer_clone.commit(res);
2156                    // Yield to cause maximum interleaving
2157                    std::thread::yield_now();
2158                }
2159            });
2160            handles.push(handle);
2161        }
2162
2163        for handle in handles {
2164            handle.join().unwrap();
2165        }
2166        writers_done.store(true, Ordering::Release);
2167        let all_messages = reader_handle.join().unwrap();
2168        let messages_read = all_messages.len();
2169        check_all_message_data(&all_messages, num_threads as u32);
2170        let dropped = buffer.dropped_pages();
2171        println!(
2172            "Reader high contention completed. Messages read = {}, dropped pages = {}",
2173            messages_read, dropped
2174        );
2175        // Verify that the total number of read messages + dropped pages' messages covers all writes.
2176        let messages_per_page =
2177            ((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE) / TestMessage::SIZE;
2178        let total_written = num_threads * msgs_per_thread;
2179        let total_accounted = messages_read + (dropped as usize * messages_per_page);
2180        assert!(total_accounted >= total_written - messages_per_page);
2181    }
2182
2183    #[test]
2184    fn test_failed_reservation_offset_boundary() {
2185        let buffer =
2186            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2187                .unwrap();
2188        let page_size = (*PAGE_SIZE) as usize;
2189        let max_payload = page_size - LocklessRingBuffer::PAGE_HEADER_SIZE;
2190
2191        // Reserve enough space to almost fill Node 0
2192        let msg_size = 100;
2193        let num_msgs = max_payload / msg_size;
2194        for _ in 0..num_msgs {
2195            let (res, _, _) = buffer.reserve(msg_size).unwrap();
2196            buffer.commit(res);
2197        }
2198
2199        // Check current write_offset of Node 0
2200        let expected_offset = LocklessRingBuffer::PAGE_HEADER_SIZE + num_msgs * msg_size;
2201        assert_eq!(buffer.nodes[0].write_offset.load(Ordering::Relaxed), expected_offset);
2202
2203        // Now try a reservation that exceeds remaining space on Node 0.
2204        let (res, _, _) = buffer.reserve(msg_size).unwrap();
2205        assert_eq!(res.node_idx, 1);
2206        buffer.commit(res);
2207
2208        // The failed reservation must not advance Node 0's write_offset, and finalizing Node 0
2209        // sealed it at that offset.
2210        assert_eq!(
2211            buffer.nodes[0].write_offset.load(Ordering::Relaxed),
2212            expected_offset | WRITE_OFFSET_SEALED_BIT
2213        );
2214    }
2215
2216    #[test]
2217    fn test_sealed_write_offset_prevents_late_small_reservation() {
2218        let buffer =
2219            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
2220                .unwrap();
2221        let page_size = (*PAGE_SIZE) as usize;
2222        let header_size = LocklessRingBuffer::PAGE_HEADER_SIZE;
2223
2224        // Leave 80 bytes remaining on Node 0.
2225        let initial_size = page_size - header_size - 80;
2226        let (res0, _, _) = buffer.reserve(initial_size).unwrap();
2227        res0.write_at(0, &vec![0xAA; initial_size]);
2228        buffer.commit(res0);
2229
2230        // A late writer registers on Node 0 (increments `active_writers` while `PAGE_ACTIVE_BIT`
2231        // is still set) but has not claimed space yet when another writer finalizes Node 0.
2232        // Relaxed: this test is single-threaded. `PAGE_ACTIVE_BIT` is set, so `reserve()`'s
2233        // registration CAS would succeed here too.
2234        buffer.nodes[0].active_writers.fetch_add(1, Ordering::Relaxed);
2235        buffer.nodes[0].finalize();
2236
2237        // The late writer now tries a 32-byte claim. 32 bytes fit in the 80 bytes left, but
2238        // Node 0 is sealed, so the claim must fail. Otherwise the record would sit past Node 0's
2239        // published `data_size` and readers would never see it.
2240        assert!(buffer.try_reserve_on_page(&buffer.nodes[0], 32).is_err());
2241        // The rejected claim left the sealed offset unchanged.
2242        assert_eq!(
2243            buffer.nodes[0].write_offset.load(Ordering::Relaxed),
2244            (header_size + initial_size) | WRITE_OFFSET_SEALED_BIT
2245        );
2246        buffer.nodes[0].release_writer();
2247
2248        // The writer retries through `reserve()` and lands on Node 1.
2249        let (res_small, _, _) = buffer.reserve(32).unwrap();
2250        assert_eq!(res_small.node_idx, 1);
2251        buffer.commit(res_small);
2252
2253        let mut dest = VecOutputBuffer::new(page_size);
2254        let bytes_read = buffer.read(&mut dest).unwrap();
2255        assert_eq!(bytes_read, page_size);
2256        assert_eq!(read_data_size(dest.data()), initial_size);
2257    }
2258
2259    #[test]
2260    fn test_advance_reopens_page_sealed_while_empty() {
2261        // 4 nodes: ring 0 -> 1 -> 2 -> 0, and reader page 3.
2262        let buffer =
2263            LocklessRingBuffer::new(4 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
2264                .unwrap();
2265        let page_size = (*PAGE_SIZE) as usize;
2266        let header_size = LocklessRingBuffer::PAGE_HEADER_SIZE;
2267        let record_size = page_size - header_size;
2268
2269        // A stale thread finalizes Node 1 before the tail reaches it. This is the state a stale
2270        // `finalize()` leaves on a page that `swap_reader_page()` re-inserted: sealed at
2271        // `PAGE_HEADER_SIZE`, `PAGE_ACTIVE_BIT` clear, `PAGE_FINALIZED_BIT` set, `data_size == 0`.
2272        buffer.nodes[1].finalize();
2273        assert_eq!(
2274            buffer.nodes[1].write_offset.load(Ordering::Relaxed),
2275            header_size | WRITE_OFFSET_SEALED_BIT
2276        );
2277        assert_eq!(
2278            buffer.nodes[1].active_writers.load(Ordering::Relaxed) & FLAGS_MASK,
2279            PAGE_FINALIZED_BIT
2280        );
2281
2282        // Fill Node 0, so that the next reservation advances the tail to Node 1.
2283        let (res0, _, _) = buffer.reserve(record_size).unwrap();
2284        assert_eq!(res0.node_idx, 0);
2285        res0.write_at(0, &vec![0xAA; record_size]);
2286        buffer.commit(res0);
2287
2288        // The advance resets Node 1, because its offset is sealed rather than exactly
2289        // `PAGE_HEADER_SIZE`, so the reservation lands on Node 1 instead of skipping it.
2290        let (res1, _, _) = buffer.reserve(record_size).unwrap();
2291        assert_eq!(res1.node_idx, 1);
2292        assert_eq!(buffer.tail_page.load(Ordering::Relaxed).index(), 1);
2293        assert_eq!(
2294            buffer.nodes[1].active_writers.load(Ordering::Relaxed),
2295            PAGE_ACTIVE_BIT | 1,
2296            "Node 1 must be active again, with the one writer registered"
2297        );
2298        res1.write_at(0, &vec![0xBB; record_size]);
2299        buffer.commit(res1);
2300
2301        // Move the tail to Node 2, so that Node 1 is finalized with its record.
2302        let (res2, _, _) = buffer.reserve(32).unwrap();
2303        assert_eq!(res2.node_idx, 2);
2304        buffer.commit(res2);
2305
2306        // The reader gets Node 0 and then Node 1, each with a full record.
2307        for fill in [0xAA, 0xBB] {
2308            let mut dest = VecOutputBuffer::new(page_size);
2309            assert_eq!(buffer.read(&mut dest).unwrap(), page_size);
2310            assert_eq!(read_data_size(dest.data()), record_size);
2311            assert!(dest.data()[header_size..].iter().all(|&b| b == fill));
2312        }
2313    }
2314
2315    #[test]
2316    fn test_stale_tail_advance_across_wraparound() {
2317        let buffer =
2318            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2319                .unwrap();
2320        let max_payload = (*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE;
2321
2322        // Capture the initial tail_val on Node 0 (generation 0, index 0).
2323        let stale_tail_val = buffer.tail_page.load(Ordering::Acquire);
2324        assert_eq!(stale_tail_val, TailVal::INITIAL);
2325
2326        // Fill Node 0 and advance to Node 1.
2327        let (res0, _, _) = buffer.reserve(max_payload).unwrap();
2328        buffer.commit(res0);
2329        let (res1, _, _) = buffer.reserve(max_payload).unwrap();
2330        assert_eq!(res1.node_idx, 1);
2331        buffer.commit(res1);
2332
2333        // Overflow Node 1, which forces an overwrite back onto Node 0 and moves head_page to
2334        // Node 1.
2335        let (res2, _, _) = buffer.reserve(100).unwrap();
2336        assert_eq!(res2.node_idx, 0);
2337        buffer.commit(res2);
2338
2339        // Now tail_page points to Node 0 again (with an incremented generation counter),
2340        // head_page is Node 1, and nodes[0].next has FLAG_HEADER pointing to Node 1.
2341        let current_tail_val = buffer.tail_page.load(Ordering::Acquire);
2342        assert_eq!(current_tail_val.index(), 0);
2343        assert_eq!(current_tail_val.generation(), 2);
2344        assert_eq!(buffer.head_page.load(Ordering::Acquire), 1);
2345        assert_eq!(buffer.dropped_pages(), 1);
2346
2347        // Simulate a delayed thread from generation 0 calling advance_to_next_page with
2348        // `stale_tail_val`. It must short-circuit at the entry check and NOT finalize Node 0, NOT
2349        // overwrite Node 1, and NOT advance `tail_page` or `head_page`. This covers only the entry
2350        // check. The re-check in the reset loop and the `tail_page` CAS would need the thread to
2351        // be paused inside `advance_to_next_page()`.
2352        let mut yield_tracker = YieldTracker::default();
2353        let res = buffer.advance_to_next_page(&buffer.nodes[0], stale_tail_val, &mut yield_tracker);
2354        assert_eq!(res, AdvanceResult::Advanced);
2355        assert_eq!(
2356            buffer.tail_page.load(Ordering::Acquire),
2357            current_tail_val,
2358            "Stale advance_to_next_page must not advance tail_page on a later generation"
2359        );
2360        assert_eq!(buffer.head_page.load(Ordering::Acquire), 1);
2361        assert_eq!(buffer.dropped_pages(), 1);
2362        assert_ne!(
2363            buffer.nodes[0].active_writers.load(Ordering::Acquire) & PAGE_ACTIVE_BIT,
2364            0,
2365            "Stale advance_to_next_page must not clear PAGE_ACTIVE_BIT on the active tail page"
2366        );
2367
2368        // Subsequent reservation on Node 0 should still succeed on Node 0.
2369        let (res3, _, _) = buffer.reserve(100).unwrap();
2370        assert_eq!(res3.node_idx, 0);
2371        buffer.commit(res3);
2372    }
2373
2374    #[test]
2375    fn test_new_rejects_more_nodes_than_tail_val_can_index() {
2376        // The check runs before the VMO is created, so this allocates nothing.
2377        let size_bytes = (TailVal::MAX_NODES + 1) * (*PAGE_SIZE) as usize;
2378        assert_eq!(
2379            LocklessRingBuffer::new(size_bytes, true, fuchsia_trace::Id::new()).err(),
2380            Some(errno!(ENOMEM))
2381        );
2382    }
2383
2384    #[test]
2385    fn test_enable_on_new_ring_returns_creation_time() {
2386        let before_new = zx::BootInstant::get();
2387        let buffer =
2388            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2389                .unwrap();
2390        let after_new = zx::BootInstant::get();
2391
2392        // `new()` creates the ring enabled, so enable() is a no-op that returns the time of the
2393        // reset done by `new()`.
2394        let created_at = buffer.enable().unwrap();
2395        assert!(before_new <= created_at && created_at <= after_new);
2396    }
2397
2398    // Holds a reservation on the same thread in place of a writer racing on another thread, which
2399    // is enough because the reservation holds its references until commit.
2400    #[test]
2401    fn test_enable_with_reservation_in_flight_is_noop() {
2402        let buffer =
2403            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2404                .unwrap();
2405
2406        // Start from a reset, as production does, so the reset time is known.
2407        buffer.disable().unwrap();
2408        let enabled_at = buffer.enable().unwrap();
2409
2410        // Hold an in-flight reservation on node 0 while calling enable() on the enabled ring.
2411        let (res, _, _) = buffer.reserve(100).unwrap();
2412        assert_eq!(res.node_idx, 0);
2413        let ref_before = buffer.ref_count.load(Ordering::Acquire);
2414
2415        // The no-op enable() returns the time of the last reset and leaves node 0 alone.
2416        assert_eq!(buffer.enable(), Ok(enabled_at));
2417        assert_eq!(buffer.ref_count.load(Ordering::Acquire), ref_before);
2418        assert_eq!(
2419            buffer.nodes[0].write_offset.load(Ordering::Relaxed),
2420            LocklessRingBuffer::PAGE_HEADER_SIZE + 100
2421        );
2422
2423        // Committing the in-flight reservation must not underflow active_writers or ref_count.
2424        buffer.commit(res);
2425        assert_eq!(buffer.ref_count.load(Ordering::Acquire), RING_ENABLED_BIT);
2426        assert_eq!(buffer.nodes[0].active_writers.load(Ordering::Acquire) & ACTIVE_WRITERS_MASK, 0);
2427    }
2428
2429    // `read()` takes its reference before it calls `available()`, so calling enable() from
2430    // `available()` runs it, on the same thread, while the reader holds that reference.
2431    #[test]
2432    fn test_enable_with_read_in_flight_is_noop() {
2433        struct EnablingOutputBuffer<'a> {
2434            buffer: &'a LocklessRingBuffer,
2435            enabled_at: zx::BootInstant,
2436        }
2437        impl std::fmt::Debug for EnablingOutputBuffer<'_> {
2438            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2439                f.debug_struct("EnablingOutputBuffer").finish_non_exhaustive()
2440            }
2441        }
2442        impl OutputBuffer for EnablingOutputBuffer<'_> {
2443            fn write_each(
2444                &mut self,
2445                _callback: &mut OutputBufferCallback<'_>,
2446            ) -> Result<usize, Errno> {
2447                Ok(0)
2448            }
2449            fn available(&self) -> usize {
2450                // The reader holds one reference. The no-op enable() must keep it.
2451                assert_eq!(self.buffer.ref_count.load(Ordering::Acquire), RING_ENABLED_BIT | 1);
2452                assert_eq!(self.buffer.enable(), Ok(self.enabled_at));
2453                assert_eq!(self.buffer.ref_count.load(Ordering::Acquire), RING_ENABLED_BIT | 1);
2454                (*PAGE_SIZE) as usize
2455            }
2456        }
2457
2458        let buffer =
2459            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2460                .unwrap();
2461        buffer.disable().unwrap();
2462        let enabled_at = buffer.enable().unwrap();
2463
2464        // The ring is empty, so read() returns EAGAIN after `available()` runs.
2465        let mut dest = EnablingOutputBuffer { buffer: &buffer, enabled_at };
2466        assert_eq!(buffer.read(&mut dest), error!(EAGAIN));
2467
2468        // Dropping the reader's reference must not underflow ref_count.
2469        assert_eq!(buffer.ref_count.load(Ordering::Acquire), RING_ENABLED_BIT);
2470    }
2471
2472    #[test]
2473    fn test_reenable_resets_ring() {
2474        let buffer =
2475            LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2476                .unwrap();
2477        let max_payload = (*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE;
2478
2479        // Move the tail to node 1, so that the reset below is observable.
2480        let (res, _, _) = buffer.reserve(100).unwrap();
2481        assert_eq!(res.node_idx, 0);
2482        buffer.commit(res);
2483        let (res, _, _) = buffer.reserve(max_payload).unwrap();
2484        assert_eq!(res.node_idx, 1);
2485        buffer.commit(res);
2486        let tail_before = buffer.tail_page.load(Ordering::Acquire);
2487        assert_eq!(tail_before.index(), 1);
2488
2489        // Disabling and re-enabling resets the ring to node 0 in the next generation, and later
2490        // no-op enables return the new time. Two clock reads can be equal, so the tail and offset
2491        // checks, not the time, show that the ring was reset.
2492        buffer.disable().unwrap();
2493        let reenabled_at = buffer.enable().unwrap();
2494        assert_eq!(buffer.tail_page.load(Ordering::Acquire), tail_before.next(0));
2495        assert_eq!(
2496            buffer.nodes[0].write_offset.load(Ordering::Relaxed),
2497            LocklessRingBuffer::PAGE_HEADER_SIZE
2498        );
2499        assert_eq!(buffer.enable(), Ok(reenabled_at));
2500    }
2501}