1use 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
22pub type OutputBufferCallback<'a> = dyn FnMut(&mut [MaybeUninit<u8>]) -> Result<usize, Errno> + 'a;
24
25pub trait OutputBuffer {
27 fn available(&self) -> usize;
29 fn write_each(&mut self, callback: &mut OutputBufferCallback<'_>) -> Result<usize, Errno>;
31}
32
33#[derive(RcuDroppable)]
34struct Node {
35 page_data: SharedBuffer,
37 next: AtomicUsize,
40 prev: AtomicUsize,
41 write_offset: AtomicUsize,
91 active_writers: AtomicUsize,
93}
94
95const FLAG_MASK: usize = 0b11 << 62;
96const FLAG_NORMAL: usize = 0b00 << 62;
98const FLAG_HEADER: usize = 0b01 << 62;
100const FLAG_UPDATE: usize = 0b10 << 62;
102
103const PAGE_ACTIVE_BIT: usize = 1 << 63;
106const PAGE_FINALIZED_BIT: usize = 1 << 62;
108const FLAGS_MASK: usize = PAGE_ACTIVE_BIT | PAGE_FINALIZED_BIT;
109const ACTIVE_WRITERS_MASK: usize = !FLAGS_MASK;
110
111const WRITE_OFFSET_SEALED_BIT: usize = 1 << 63;
122
123fn encode_time_stamp(ts_nanos: u64) -> [u8; LocklessRingBuffer::TIME_STAMP_RECORD_SIZE] {
127 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
139const SPIN_SLEEP_DURATION: std::time::Duration = std::time::Duration::from_micros(50);
144
145const PROGRESSIVE_SLEEP_DURATION: std::time::Duration = std::time::Duration::from_millis(10);
149
150impl Node {
151 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 fn finalize(&self) {
168 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 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 self.active_writers.fetch_and(!PAGE_ACTIVE_BIT, Ordering::Release);
215 }
216
217 fn release_writer(&self) {
219 let prev_writers = self.active_writers.fetch_sub(1, Ordering::Release);
225
226 if prev_writers == PAGE_ACTIVE_BIT | 1 {
230 std::sync::atomic::fence(Ordering::Acquire);
236
237 let write_offset = Node::unsealed(self.write_offset.load(Ordering::Relaxed));
242 if write_offset >= (*PAGE_SIZE) as usize {
243 self.finalize();
249 }
250 }
251 }
252}
253
254#[cfg(test)]
257#[derive(Clone, Copy, Debug, PartialEq, Eq)]
258enum TestHook {
259 BeforeClaim,
261 AfterClaim,
263}
264
265#[cfg(test)]
266thread_local! {
267 static TEST_HOOK: std::cell::RefCell<Option<(TestHook, Box<dyn FnOnce(&LocklessRingBuffer)>)>> =
270 const { std::cell::RefCell::new(None) };
271}
272
273#[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#[cfg(test)]
282fn test_hook_is_armed() -> bool {
283 TEST_HOOK.with(|h| h.borrow().is_some())
284}
285
286#[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
299static_assertions::const_assert!(std::mem::size_of::<usize>() == 8);
301
302const RING_ENABLED_BIT: usize = 1 << 63;
305
306#[derive(RcuDroppable)]
307pub struct LocklessRingBuffer {
308 vmo: zx::Vmo,
309 mapping: SharedBuffer,
310 nodes: Vec<Node>,
312 head_page: AtomicUsize,
314 tail_page: AtomicTailVal,
316
317 reader_page: AtomicUsize,
319
320 ref_count: AtomicUsize,
323 overwrite: bool,
326 dropped_pages: std::sync::atomic::AtomicU64,
328 prev_timestamp: std::sync::atomic::AtomicU64,
331 write_event_async_id: fuchsia_trace::Id,
333 reader_active: std::sync::atomic::AtomicBool,
337 state_mutex: LockDepMutex<zx::BootInstant, PerfRingBufferStateLock>,
341}
342impl LocklessRingBuffer {
343 pub const PAGE_HEADER_SIZE: usize = 16;
345
346 pub const TYPE_OR_LENGTH_BITS: u32 = 5;
351 pub const TIME_DELTA_BITS: u32 = 27;
354 pub const MAX_TIME_DELTA_NANOS: u64 = (1 << Self::TIME_DELTA_BITS) - 1;
356 pub const RINGBUF_TYPE_TIME_STAMP: u32 = 31;
359 pub const TIME_STAMP_RECORD_SIZE: usize = 8;
361 const MAX_TIME_STAMP_NANOS: u64 = (1 << (Self::TIME_DELTA_BITS + u32::BITS)) - 1;
363
364 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 let pages = std::cmp::max(3, requested_pages);
376 let total_nodes = pages;
377 if total_nodes > TailVal::MAX_NODES {
379 return error!(ENOMEM);
380 }
381 let capacity = total_nodes * (*PAGE_SIZE) as usize;
382
383 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 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 let mapping = unsafe { SharedBuffer::new(addr as *mut u8, capacity) };
395
396 let mut nodes = Vec::with_capacity(total_nodes);
398 let base_ptr = addr as *mut u8;
399 for i in 0..total_nodes {
400 let page_ptr = unsafe { base_ptr.add(i * (*PAGE_SIZE) as usize) };
404
405 nodes.push(Node {
406 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 active_writers: AtomicUsize::new(PAGE_ACTIVE_BIT),
414 });
415 }
416 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 nodes[circle_size - 1].next.store(Node::make_val(0, FLAG_HEADER), Ordering::Relaxed);
426 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 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#[derive(Default, Debug)]
460struct YieldTracker {
461 update_flag: u64,
463 node_match: u64,
465 head_lock: u64,
467}
468impl YieldTracker {
469 fn total(&self) -> u64 {
470 self.update_flag + self.node_match + self.head_lock
471 }
472
473 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
486pub struct Reservation<'a> {
488 pub offset: usize,
489 pub node_idx: usize,
490 pub size: usize,
491 header_time_delta: u32,
493 buffer: &'a LocklessRingBuffer,
494 committed: bool,
495}
496
497impl<'a> Reservation<'a> {
498 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 Advanced,
550 Yielded,
552 Error(Errno),
554}
555
556struct PageReservation {
558 offset: usize,
560 now: zx::BootInstant,
562 delta: zx::Duration<zx::BootTimeline>,
564 header_time_delta: u32,
566}
567
568impl LocklessRingBuffer {
569 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 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 let prev_estimate = self.prev_timestamp.load(Ordering::Relaxed);
599 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 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 tail_node.page_data.write_at(current_offset, &encode_time_stamp(final_now_nanos));
655 0
656 } else {
657 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 fn advance_to_next_page(
683 &self,
684 tail_node: &Node,
685 tail_val: TailVal,
686 yield_tracker: &mut YieldTracker,
687 ) -> AdvanceResult {
688 if self.tail_page.load(Ordering::Acquire) != tail_val {
690 return AdvanceResult::Advanced;
691 }
692
693 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 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 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 let expected_next = Node::make_val(next_idx, FLAG_HEADER);
742 let locked_next = Node::make_val(next_idx, FLAG_UPDATE);
743 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 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 let new_head_next = Node::make_val(head_next_idx, FLAG_HEADER);
764 head_node.next.store(new_head_next, Ordering::Release);
765
766 self.head_page.store(head_next_idx, Ordering::Release);
768
769 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 yield_tracker.head_lock += 1;
782 yield_tracker.yield_or_sleep();
783 return AdvanceResult::Yielded;
784 }
785 } else {
786 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 let (should_advance, won_reset) = {
799 let mut current = next_node.write_offset.load(Ordering::Relaxed);
801 loop {
802 if self.tail_page.load(Ordering::Acquire) != tail_val {
804 break (false, false);
805 }
806 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 next_node.active_writers.store(PAGE_ACTIVE_BIT, Ordering::Release);
830 next_node.page_data.write_at(8, &0u64.to_le_bytes());
832 }
833
834 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 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 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 if size == 0 || size > (*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE {
890 return error!(EINVAL);
891 }
892 let mut val = self.ref_count.load(Ordering::Acquire);
894 loop {
895 if val & RING_ENABLED_BIT == 0 {
896 return error!(ENOMEM);
897 }
898 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 let ref_count_guard = scopeguard::guard(&self.ref_count, |ref_count| {
921 ref_count.fetch_sub(1, Ordering::Release);
922 });
923
924 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 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, ¤t_boot_time)
962 } else {
963 Err(())
964 };
965
966 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 if active_incremented {
989 self.nodes[tail_idx].release_writer();
990 }
991 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 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 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 scopeguard::ScopeGuard::into_inner(ref_count_guard);
1020 Ok((res, now, delta))
1021 }
1022 Err(e) => {
1023 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 return None;
1042 }
1043 let head_node = &self.nodes[head_idx];
1044 let active_writers = head_node.active_writers.load(Ordering::Acquire);
1047 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 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 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 let reader_idx = self.reader_page.load(Ordering::Acquire);
1083 let next_node = &self.nodes[next_idx];
1084 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 self.nodes[reader_idx]
1094 .write_offset
1095 .store(LocklessRingBuffer::PAGE_HEADER_SIZE, Ordering::Relaxed);
1096 self.nodes[reader_idx].active_writers.store(PAGE_ACTIVE_BIT, Ordering::Relaxed);
1098 self.nodes[reader_idx].page_data.write_at(8, &0u64.to_le_bytes());
1100 let unlocked_val = Node::make_val(reader_idx, FLAG_NORMAL);
1102 prev_node.next.store(unlocked_val, Ordering::Release);
1103 self.head_page.store(next_idx, Ordering::Release);
1105 self.reader_page.store(head_idx, Ordering::Release);
1106 return Some(head_idx);
1108 }
1109 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 std::hint::spin_loop();
1120 }
1121 }
1122 pub fn read(&self, buf: &mut dyn OutputBuffer) -> Result<usize, Errno> {
1123 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 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 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 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 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 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 pub fn enable(&self) -> Result<zx::BootInstant, Errno> {
1243 let mut last_reset_guard = self.state_mutex.lock();
1244 if self.is_enabled() {
1248 return Ok(*last_reset_guard);
1249 }
1250 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 self.head_page.store(0, Ordering::Release);
1269 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 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 self.nodes[initial_pages].page_data.write_at(8, &0u64.to_le_bytes());
1309 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 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 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 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 self.data.extend(segment[..written].iter().map(|b| unsafe { b.assume_init() }));
1362 Ok(written)
1363 }
1364 }
1365
1366 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 fn reserve_without_gap(
1379 buffer: &LocklessRingBuffer,
1380 size: usize,
1381 ) -> Result<(Reservation<'_>, zx::BootInstant, zx::Duration<zx::BootTimeline>), Errno> {
1382 let now = buffer.prev_timestamp.load(Ordering::Relaxed) + 1;
1385 reserve_at(buffer, size, zx::BootInstant::from_nanos(now as i64))
1386 }
1387
1388 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 #[derive(Debug, Clone, PartialEq)]
1401 struct DecodedRecord {
1402 timestamp: u64,
1403 payload: Vec<u8>,
1404 }
1405
1406 fn injected_start(buffer: &LocklessRingBuffer) -> u64 {
1408 buffer.prev_timestamp.load(Ordering::Relaxed) + 1_000
1410 }
1411
1412 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 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 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 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 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 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 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 let res1 =
1616 buffer.reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE).unwrap();
1617 buffer.commit(res1.0);
1618 let res2 = buffer.reserve(100).unwrap();
1620 assert_eq!(res2.0.node_idx, 1);
1621 buffer.commit(res2.0);
1622 let old_head = buffer.swap_reader_page();
1625 assert_eq!(old_head, Some(0));
1626 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 let res1 = buffer
1662 .reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE - 50)
1663 .unwrap();
1664 buffer.commit(res1.0);
1665 let res2 = buffer.reserve(100).unwrap();
1667 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 let res1 =
1678 buffer.reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE).unwrap();
1679 buffer.commit(res1.0);
1680 let _res2 =
1682 buffer.reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE).unwrap();
1683 let res3 = buffer.reserve(100).unwrap();
1687 assert_eq!(res3.0.node_idx, 0);
1689 assert_eq!(buffer.dropped_pages(), 1);
1690 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 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 #[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 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 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 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 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 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 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 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 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 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_all_message_data(&all_messages, num_threads);
1908 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 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 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 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 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 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 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 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 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_all_message_data(&all_messages, num_threads);
2033 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 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 let handle = std::thread::spawn(move || {
2059 buffer_clone.disable().unwrap();
2060 disable_finished_clone.store(true, Ordering::Relaxed);
2061 });
2062 while buffer.ref_count.load(Ordering::Acquire) & RING_ENABLED_BIT != 0 {
2064 std::hint::spin_loop();
2065 }
2066 assert!(!disable_finished.load(Ordering::Relaxed));
2068 let res2 = buffer.reserve(100);
2070 assert_eq!(res2.unwrap_err(), starnix_uapi::errno!(ENOMEM));
2071 let data = vec![42u8; 100];
2074 res.0.write_at(0, &data);
2075 buffer.commit(res.0);
2076 handle.join().unwrap();
2078 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 let res = buffer.reserve(100).unwrap();
2088 assert_eq!(buffer.ref_count.load(Ordering::Relaxed), RING_ENABLED_BIT | 1);
2089 std::mem::drop(res.0);
2091 assert_eq!(buffer.ref_count.load(Ordering::Relaxed), RING_ENABLED_BIT);
2093 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 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 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 let res1 =
2143 buffer.reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE).unwrap();
2144 buffer.commit(res1.0);
2145 let res2 =
2147 buffer.reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE).unwrap();
2148 buffer.commit(res2.0);
2149 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 let buffer = Arc::new(
2169 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2170 .unwrap(),
2171 );
2172 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 let reader_handle = start_gated_reader_thread(buffer_reader, writers_done_reader, true);
2197
2198 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 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_all_message_data(&all_messages, num_threads);
2226 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 let (res1, _, _) = reserve_without_gap(&buffer, size1).unwrap();
2245 let (res2, _, _) = reserve_without_gap(&buffer, size2).unwrap();
2247
2248 buffer.commit(res2);
2250
2251 assert!(buffer.swap_reader_page().is_none());
2253
2254 buffer.commit(res1);
2256
2257 let _ = reserve_without_gap(&buffer, 10).unwrap();
2259
2260 let swapped = buffer.swap_reader_page();
2262 assert!(swapped.is_some());
2263 assert_eq!(swapped.unwrap(), 0); }
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 self.barrier_in_available.wait();
2284
2285 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 barrier_in_available.wait();
2313
2314 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 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 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 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 let res1 = reserve_without_gap(&buffer, 100).unwrap();
2375 assert_eq!(res1.0.node_idx, 0);
2376
2377 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 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 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 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 std::thread::sleep(std::time::Duration::from_millis(50));
2410 assert!(!writer_finished.load(Ordering::Acquire));
2411
2412 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 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 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 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 let _dropped = buffer_clone.disable().unwrap();
2466
2467 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 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 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 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 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 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 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 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 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 let (res, _, _) = reserve_without_gap(&buffer, msg_size).unwrap();
2568 assert_eq!(res.node_idx, 1);
2569 buffer.commit(res);
2570
2571 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 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 buffer.nodes[0].active_writers.fetch_add(1, Ordering::Relaxed);
2598 buffer.nodes[0].finalize();
2599
2600 assert!(buffer.try_reserve_on_page(&buffer.nodes[0], 32, &zx::BootInstant::get).is_err());
2604 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 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 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 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 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 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 let (res2, _, _) = buffer.reserve(32).unwrap();
2666 assert_eq!(res2.node_idx, 2);
2667 buffer.commit(res2);
2668
2669 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 let stale_tail_val = buffer.tail_page.load(Ordering::Acquire);
2687 assert_eq!(stale_tail_val, TailVal::INITIAL);
2688
2689 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 let (res2, _, _) = buffer.reserve(100).unwrap();
2699 assert_eq!(res2.node_idx, 0);
2700 buffer.commit(res2);
2701
2702 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 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 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 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 let created_at = buffer.enable().unwrap();
2758 assert!(before_new <= created_at && created_at <= after_new);
2759 }
2760
2761 #[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 buffer.disable().unwrap();
2771 let enabled_at = buffer.enable().unwrap();
2772
2773 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 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 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 #[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 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 let mut dest = EnablingOutputBuffer { buffer: &buffer, enabled_at };
2829 assert_eq!(buffer.read(&mut dest), error!(EAGAIN));
2830
2831 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 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 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 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 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 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 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 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 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 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 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 assert_eq!(write_record_at(&buffer, t0, &vec![0xaa; max_payload - 8 - 10]).0, 0);
3013
3014 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}