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