1use fuchsia_rcu::RcuDroppable;
7use fuchsia_runtime::vmar_root_self;
8use fuchsia_trace;
9use shared_buffer::SharedBuffer;
10use starnix_logging::{log_error, log_info, log_warn};
11use starnix_sync::{LockDepMutex, PerfRingBufferStateLock};
12use starnix_types::PAGE_SIZE;
13use starnix_uapi::errors::Errno;
14use starnix_uapi::{errno, error, from_status_like_fdio};
15use std::mem::MaybeUninit;
16use std::sync::atomic::{AtomicUsize, Ordering};
17
18use crate::tail_val::{AtomicTailVal, TailVal};
19
20pub type OutputBufferCallback<'a> = dyn FnMut(&mut [MaybeUninit<u8>]) -> Result<usize, Errno> + 'a;
22
23pub trait OutputBuffer {
25 fn available(&self) -> usize;
27 fn write_each(&mut self, callback: &mut OutputBufferCallback<'_>) -> Result<usize, Errno>;
29}
30
31#[derive(RcuDroppable)]
32struct Node {
33 page_data: SharedBuffer,
35 next: AtomicUsize,
38 prev: AtomicUsize,
39 write_offset: AtomicUsize,
89 active_writers: AtomicUsize,
91}
92
93const FLAG_MASK: usize = 0b11 << 62;
94const FLAG_NORMAL: usize = 0b00 << 62;
96const FLAG_HEADER: usize = 0b01 << 62;
98const FLAG_UPDATE: usize = 0b10 << 62;
100
101const PAGE_ACTIVE_BIT: usize = 1 << 63;
104const PAGE_FINALIZED_BIT: usize = 1 << 62;
106const FLAGS_MASK: usize = PAGE_ACTIVE_BIT | PAGE_FINALIZED_BIT;
107const ACTIVE_WRITERS_MASK: usize = !FLAGS_MASK;
108
109const WRITE_OFFSET_SEALED_BIT: usize = 1 << 63;
120
121const SPIN_SLEEP_DURATION: std::time::Duration = std::time::Duration::from_micros(50);
126
127const PROGRESSIVE_SLEEP_DURATION: std::time::Duration = std::time::Duration::from_millis(10);
131
132impl Node {
133 fn unsealed(raw_write_offset: usize) -> usize {
135 raw_write_offset & !WRITE_OFFSET_SEALED_BIT
136 }
137
138 fn get_index(val: usize) -> usize {
139 val & !FLAG_MASK
140 }
141 fn get_flags(val: usize) -> usize {
142 val & FLAG_MASK
143 }
144 fn make_val(index: usize, flags: usize) -> usize {
145 index | flags
146 }
147
148 fn finalize(&self) {
150 let old_val = self.active_writers.fetch_or(PAGE_FINALIZED_BIT, Ordering::AcqRel);
170 if old_val & PAGE_FINALIZED_BIT != 0 {
171 return;
172 }
173
174 let write_offset =
179 Node::unsealed(self.write_offset.fetch_or(WRITE_OFFSET_SEALED_BIT, Ordering::Relaxed));
180
181 debug_assert!(
182 (LocklessRingBuffer::PAGE_HEADER_SIZE..=(*PAGE_SIZE) as usize).contains(&write_offset)
183 );
184 let data_size = write_offset - LocklessRingBuffer::PAGE_HEADER_SIZE;
185 self.page_data.write_at(8, &(data_size as u64).to_le_bytes());
186
187 self.active_writers.fetch_and(!PAGE_ACTIVE_BIT, Ordering::Release);
197 }
198
199 fn release_writer(&self) {
201 let prev_writers = self.active_writers.fetch_sub(1, Ordering::Release);
207
208 if prev_writers == PAGE_ACTIVE_BIT | 1 {
212 std::sync::atomic::fence(Ordering::Acquire);
218
219 let write_offset = Node::unsealed(self.write_offset.load(Ordering::Relaxed));
224 if write_offset >= (*PAGE_SIZE) as usize {
225 self.finalize();
231 }
232 }
233 }
234}
235
236static_assertions::const_assert!(std::mem::size_of::<usize>() == 8);
238
239const RING_ENABLED_BIT: usize = 1 << 63;
242
243#[derive(RcuDroppable)]
244pub struct LocklessRingBuffer {
245 vmo: zx::Vmo,
246 mapping: SharedBuffer,
247 nodes: Vec<Node>,
249 head_page: AtomicUsize,
251 tail_page: AtomicTailVal,
253
254 reader_page: AtomicUsize,
256
257 ref_count: AtomicUsize,
260 overwrite: bool,
263 dropped_pages: std::sync::atomic::AtomicU64,
265 prev_timestamp: std::sync::atomic::AtomicU64,
268 write_event_async_id: fuchsia_trace::Id,
270 reader_active: std::sync::atomic::AtomicBool,
274 state_mutex: LockDepMutex<zx::BootInstant, PerfRingBufferStateLock>,
278}
279impl LocklessRingBuffer {
280 pub const PAGE_HEADER_SIZE: usize = 16;
282
283 pub fn new(
288 size_bytes: usize,
289 overwrite: bool,
290 write_event_async_id: fuchsia_trace::Id,
291 ) -> Result<Self, Errno> {
292 let requested_pages = size_bytes.div_ceil((*PAGE_SIZE) as usize);
293 let pages = std::cmp::max(3, requested_pages);
295 let total_nodes = pages;
296 if total_nodes > TailVal::MAX_NODES {
298 return error!(ENOMEM);
299 }
300 let capacity = total_nodes * (*PAGE_SIZE) as usize;
301
302 let vmo = starnix_logging::with_zx_name(
304 zx::Vmo::create_with_opts(zx::VmoOptions::RESIZABLE, capacity as u64)
305 .map_err(|_| errno!(ENOMEM))?,
306 b"starnix:tracefs",
307 );
308 let addr = vmar_root_self()
310 .map(0, &vmo, 0, capacity, zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE)
311 .map_err(|e| from_status_like_fdio!(e))?;
312 let mapping = unsafe { SharedBuffer::new(addr as *mut u8, capacity) };
314
315 let mut nodes = Vec::with_capacity(total_nodes);
317 let base_ptr = addr as *mut u8;
318 for i in 0..total_nodes {
319 let page_ptr = unsafe { base_ptr.add(i * (*PAGE_SIZE) as usize) };
323
324 nodes.push(Node {
325 page_data: unsafe { SharedBuffer::new(page_ptr, (*PAGE_SIZE) as usize) },
327 next: AtomicUsize::new(0),
328 prev: AtomicUsize::new(0),
329 write_offset: AtomicUsize::new(LocklessRingBuffer::PAGE_HEADER_SIZE),
330 active_writers: AtomicUsize::new(PAGE_ACTIVE_BIT),
333 });
334 }
335 let circle_size = pages - 1;
337 for i in 0..circle_size {
338 let next_idx = (i + 1) % circle_size;
339 let prev_idx = (i + circle_size - 1) % circle_size;
340 nodes[i].next.store(Node::make_val(next_idx, FLAG_NORMAL), Ordering::Relaxed);
341 nodes[i].prev.store(Node::make_val(prev_idx, FLAG_NORMAL), Ordering::Relaxed);
342 }
343 nodes[circle_size - 1].next.store(Node::make_val(0, FLAG_HEADER), Ordering::Relaxed);
345 nodes[pages - 1].next.store(Node::make_val(0, FLAG_NORMAL), Ordering::Relaxed);
347 nodes[pages - 1]
348 .prev
349 .store(Node::make_val(circle_size - 1, FLAG_NORMAL), Ordering::Relaxed);
350 nodes[pages - 1].page_data.write_at(8, &0u64.to_le_bytes());
352 let now = zx::BootInstant::get();
353 let buffer = Self {
354 vmo,
355 mapping,
356 nodes,
357 head_page: AtomicUsize::new(0),
358 tail_page: AtomicTailVal::new(TailVal::INITIAL),
359 reader_page: AtomicUsize::new(pages - 1),
360
361 ref_count: AtomicUsize::new(RING_ENABLED_BIT),
362 overwrite,
363 dropped_pages: std::sync::atomic::AtomicU64::new(0),
364 prev_timestamp: std::sync::atomic::AtomicU64::new(now.into_nanos() as u64),
365 write_event_async_id,
366 reader_active: std::sync::atomic::AtomicBool::new(false),
367 state_mutex: LockDepMutex::new(now),
368 };
369 Ok(buffer)
370 }
371 #[cfg(test)]
372 fn dropped_pages(&self) -> u64 {
373 self.dropped_pages.load(Ordering::Relaxed)
374 }
375}
376#[derive(Default, Debug)]
379struct YieldTracker {
380 update_flag: u64,
382 node_match: u64,
384 head_lock: u64,
386}
387impl YieldTracker {
388 fn total(&self) -> u64 {
389 self.update_flag + self.node_match + self.head_lock
390 }
391
392 fn yield_or_sleep(&self) {
394 let total = self.total();
395 if total > 1000 {
396 std::thread::sleep(PROGRESSIVE_SLEEP_DURATION);
397 } else if total > 100 {
398 std::thread::sleep(SPIN_SLEEP_DURATION);
399 } else {
400 std::thread::yield_now();
401 }
402 }
403}
404
405pub struct Reservation<'a> {
407 pub offset: usize,
408 pub node_idx: usize,
409 pub size: usize,
410 buffer: &'a LocklessRingBuffer,
411 committed: bool,
412}
413
414impl<'a> Reservation<'a> {
415 pub fn write_at(&self, rel_offset: usize, data: &[u8]) {
416 assert!(rel_offset + data.len() <= self.size, "Write exceeds reservation size");
417 self.buffer.mapping.write_at(self.offset + rel_offset, data);
418 }
419
420 fn release(&mut self) {
421 if self.committed {
422 return;
423 }
424
425 let node_idx = self.node_idx;
426 let node = &self.buffer.nodes[node_idx];
427
428 node.release_writer();
429
430 self.buffer.ref_count.fetch_sub(1, Ordering::Release);
431 self.committed = true;
432 }
433}
434
435impl<'a> Drop for Reservation<'a> {
436 fn drop(&mut self) {
437 if !self.committed {
438 starnix_logging::log_warn!("LocklessRingBuffer: Reservation dropped without commit");
439 }
440 self.release();
441 }
442}
443
444impl<'a> std::fmt::Debug for Reservation<'a> {
445 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
446 f.debug_struct("Reservation")
447 .field("offset", &self.offset)
448 .field("node_idx", &self.node_idx)
449 .field("size", &self.size)
450 .field("committed", &self.committed)
451 .finish()
452 }
453}
454#[derive(Debug, PartialEq, Eq)]
455enum AdvanceResult {
456 Advanced,
458 Yielded,
460 Error(Errno),
462}
463
464impl LocklessRingBuffer {
465 fn try_reserve_on_page(
470 &self,
471 tail_node: &Node,
472 size: usize,
473 ) -> Result<(usize, zx::BootInstant, zx::Duration<zx::BootTimeline>), ()> {
474 let mut current_offset = tail_node.write_offset.load(Ordering::Relaxed);
482 loop {
483 if (current_offset & WRITE_OFFSET_SEALED_BIT) != 0
484 || current_offset + size > (*PAGE_SIZE) as usize
485 {
486 return Err(());
487 }
488 match tail_node.write_offset.compare_exchange_weak(
489 current_offset,
490 current_offset + size,
491 Ordering::Relaxed,
492 Ordering::Relaxed,
493 ) {
494 Ok(_) => break,
495 Err(actual) => current_offset = actual,
496 }
497 }
498
499 let now_candidate = zx::BootInstant::get().into_nanos() as u64;
500 let actual_prev = self.prev_timestamp.fetch_max(now_candidate, Ordering::AcqRel);
501 let final_now_nanos = std::cmp::max(now_candidate, actual_prev);
502 let now = zx::BootInstant::from_nanos(final_now_nanos as i64);
503
504 let delta_nanos = if current_offset == LocklessRingBuffer::PAGE_HEADER_SIZE {
506 tail_node.page_data.write_at(0, &final_now_nanos.to_le_bytes());
507 0
508 } else {
509 final_now_nanos.saturating_sub(actual_prev)
510 };
511
512 let delta = zx::Duration::from_nanos(delta_nanos as i64);
513
514 Ok((current_offset, now, delta))
515 }
516
517 fn advance_to_next_page(
522 &self,
523 tail_node: &Node,
524 tail_val: TailVal,
525 yield_tracker: &mut YieldTracker,
526 ) -> AdvanceResult {
527 if self.tail_page.load(Ordering::Acquire) != tail_val {
529 return AdvanceResult::Advanced;
530 }
531
532 if self.is_enabled() {
547 tail_node.finalize();
548 }
549
550 let next_val = tail_node.next.load(Ordering::Acquire);
551 let next_idx = Node::get_index(next_val);
552 let next_flags = Node::get_flags(next_val);
553 if next_flags == FLAG_UPDATE {
554 starnix_logging::log_warn!(
556 "Reservation yielding due to FLAG_UPDATE on node {}",
557 next_idx
558 );
559 yield_tracker.update_flag += 1;
560 yield_tracker.yield_or_sleep();
561 return AdvanceResult::Yielded;
562 } else if next_flags == FLAG_HEADER {
563 if self.overwrite {
564 if (self.nodes[next_idx].active_writers.load(Ordering::Acquire)
566 & ACTIVE_WRITERS_MASK)
567 > 0
568 {
569 yield_tracker.node_match += 1;
570 yield_tracker.yield_or_sleep();
571 return AdvanceResult::Yielded;
572 }
573
574 starnix_logging::log_warn!(
575 "LocklessRingBuffer Overwriting page {} (overwriting={})",
576 next_idx,
577 self.overwrite
578 );
579 let expected_next = Node::make_val(next_idx, FLAG_HEADER);
581 let locked_next = Node::make_val(next_idx, FLAG_UPDATE);
582 if tail_node
587 .next
588 .compare_exchange_weak(
589 expected_next,
590 locked_next,
591 Ordering::AcqRel,
592 Ordering::Relaxed,
593 )
594 .is_ok()
595 {
596 let head_node = &self.nodes[next_idx];
598 let head_next_val = head_node.next.load(Ordering::Acquire);
599 let head_next_idx = Node::get_index(head_next_val);
600
601 let new_head_next = Node::make_val(head_next_idx, FLAG_HEADER);
603 head_node.next.store(new_head_next, Ordering::Release);
604
605 self.head_page.store(head_next_idx, Ordering::Release);
607
608 let unlocked_next = Node::make_val(next_idx, FLAG_NORMAL);
610 tail_node.next.store(unlocked_next, Ordering::Release);
611 self.dropped_pages.fetch_add(1, Ordering::Relaxed);
612 fuchsia_trace::async_instant!(
613 self.write_event_async_id,
614 "starnix:trace_meta",
615 "page dropped"
616 );
617 return AdvanceResult::Advanced;
618 } else {
619 yield_tracker.head_lock += 1;
621 yield_tracker.yield_or_sleep();
622 return AdvanceResult::Yielded;
623 }
624 } else {
625 starnix_logging::log_error!("LocklessRingBuffer is full");
627 return AdvanceResult::Error(errno!(ENOSPC));
628 }
629 }
630
631 let next_node = &self.nodes[next_idx];
632 let (should_advance, won_reset) = {
638 let mut current = next_node.write_offset.load(Ordering::Relaxed);
640 loop {
641 if self.tail_page.load(Ordering::Acquire) != tail_val {
643 break (false, false);
644 }
645 if current == LocklessRingBuffer::PAGE_HEADER_SIZE {
651 break (true, false);
652 }
653 match next_node.write_offset.compare_exchange_weak(
654 current,
655 LocklessRingBuffer::PAGE_HEADER_SIZE,
656 Ordering::Relaxed,
657 Ordering::Relaxed,
658 ) {
659 Ok(_) => break (true, true),
660 Err(actual) => current = actual,
661 }
662 }
663 };
664
665 if should_advance {
666 if won_reset {
667 next_node.active_writers.store(PAGE_ACTIVE_BIT, Ordering::Release);
669 next_node.page_data.write_at(8, &0u64.to_le_bytes());
671 }
672
673 if let Err(err) = self.tail_page.compare_exchange(
695 tail_val,
696 tail_val.next(next_idx),
697 Ordering::AcqRel,
698 Ordering::Relaxed,
699 ) {
700 starnix_logging::log_debug!(
701 "Tail page already advanced by another thread: {err:?}"
702 );
703 }
704 }
705 AdvanceResult::Advanced
706 }
707
708 pub fn reserve(
709 &self,
710 size: usize,
711 ) -> Result<(Reservation<'_>, zx::BootInstant, zx::Duration<zx::BootTimeline>), Errno> {
712 if size == 0 || size > (*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE {
714 return error!(EINVAL);
715 }
716 let mut val = self.ref_count.load(Ordering::Acquire);
718 loop {
719 if val & RING_ENABLED_BIT == 0 {
720 return error!(ENOMEM);
721 }
722 match self.ref_count.compare_exchange_weak(
733 val,
734 val + 1,
735 Ordering::Acquire,
736 Ordering::Relaxed,
737 ) {
738 Ok(_) => break,
739 Err(actual) => val = actual,
740 }
741 }
742
743 let ref_count_guard = scopeguard::guard(&self.ref_count, |ref_count| {
745 ref_count.fetch_sub(1, Ordering::Release);
746 });
747
748 let mut yield_tracker = YieldTracker::default();
750 let result = loop {
751 if !self.is_enabled() {
752 log_info!("LocklessRingBuffer: canceling reserve because ring is disabled.");
753 break error!(ENOMEM);
754 }
755 let tail_val = self.tail_page.load(Ordering::Acquire);
756 let tail_idx = tail_val.index() as usize;
757 let tail_node = &self.nodes[tail_idx];
758
759 let mut active_incremented = false;
765 let mut old_writers = tail_node.active_writers.load(Ordering::Acquire);
766 loop {
767 if old_writers & PAGE_ACTIVE_BIT == 0 {
768 break;
769 }
770 match tail_node.active_writers.compare_exchange_weak(
771 old_writers,
772 old_writers + 1,
773 Ordering::Relaxed,
774 Ordering::Relaxed,
775 ) {
776 Ok(_) => {
777 active_incremented = true;
778 break;
779 }
780 Err(actual) => old_writers = actual,
781 }
782 }
783
784 let reservation_result = if active_incremented {
785 self.try_reserve_on_page(tail_node, size)
786 } else {
787 Err(())
788 };
789
790 match reservation_result {
792 Ok((offset, now, delta)) => {
793 break Ok((
794 Reservation {
795 offset: tail_idx * (*PAGE_SIZE) as usize + offset,
796 node_idx: tail_idx,
797 size,
798 buffer: self,
799 committed: false,
800 },
801 now,
802 delta,
803 ));
804 }
805 Err(()) => {
806 if active_incremented {
812 self.nodes[tail_idx].release_writer();
813 }
814 match self.advance_to_next_page(tail_node, tail_val, &mut yield_tracker) {
816 AdvanceResult::Advanced | AdvanceResult::Yielded => {}
817 AdvanceResult::Error(e) => {
818 break Err(e);
819 }
820 }
821 }
822 }
823 if yield_tracker.total() % 100_000_000 == 0 && yield_tracker.total() > 0 {
825 log_warn!(
826 "LocklessRingBuffer: spinning in reservation loop. details = {yield_tracker:?}",
827 );
828 }
829 };
830 let total_yields = yield_tracker.total();
832 if total_yields > 500_000 {
833 starnix_logging::log_info!(
834 "Reservation completed with yields: total={}, details={:?}",
835 total_yields,
836 yield_tracker
837 );
838 }
839 match result {
840 Ok((res, now, delta)) => {
841 scopeguard::ScopeGuard::into_inner(ref_count_guard);
843 Ok((res, now, delta))
844 }
845 Err(e) => {
846 Err(e)
848 }
849 }
850 }
851 pub fn commit(&self, mut reservation: Reservation<'_>) {
852 reservation.release();
853 }
854
855 fn swap_reader_page(&self) -> Option<usize> {
856 let mut retries = 0;
857 loop {
858 let head_val = self.head_page.load(Ordering::Acquire);
859 let head_idx = Node::get_index(head_val);
860 let tail_val = self.tail_page.load(Ordering::Acquire);
861 let tail_idx = tail_val.index() as usize;
862 if head_idx == tail_idx {
863 return None;
865 }
866 let head_node = &self.nodes[head_idx];
867 let active_writers = head_node.active_writers.load(Ordering::Acquire);
870 if (active_writers & (PAGE_ACTIVE_BIT | ACTIVE_WRITERS_MASK)) != 0 {
873 return None;
874 }
875 let mut size_bytes = [0u8; 8];
876 head_node.page_data.read_at(8, &mut size_bytes);
877 let size = u64::from_le_bytes(size_bytes);
878 if size == 0 {
879 return None;
885 }
886 let next_val = head_node.next.load(Ordering::Acquire);
887 let next_idx = Node::get_index(next_val);
888 let prev_val = head_node.prev.load(Ordering::Acquire);
889 let prev_idx = Node::get_index(prev_val);
890 let prev_node = &self.nodes[prev_idx];
891 let expected_next = Node::make_val(head_idx, FLAG_HEADER);
893 let locked_next = Node::make_val(head_idx, FLAG_UPDATE);
894 if prev_node
895 .next
896 .compare_exchange_weak(
897 expected_next,
898 locked_next,
899 Ordering::AcqRel,
900 Ordering::Relaxed,
901 )
902 .is_ok()
903 {
904 let reader_idx = self.reader_page.load(Ordering::Acquire);
906 let next_node = &self.nodes[next_idx];
907 self.nodes[reader_idx]
909 .prev
910 .store(Node::make_val(prev_idx, FLAG_NORMAL), Ordering::Relaxed);
911 self.nodes[reader_idx]
912 .next
913 .store(Node::make_val(next_idx, FLAG_HEADER), Ordering::Relaxed);
914 next_node.prev.store(Node::make_val(reader_idx, FLAG_NORMAL), Ordering::Relaxed);
915 self.nodes[reader_idx]
917 .write_offset
918 .store(LocklessRingBuffer::PAGE_HEADER_SIZE, Ordering::Relaxed);
919 self.nodes[reader_idx].active_writers.store(PAGE_ACTIVE_BIT, Ordering::Relaxed);
921 self.nodes[reader_idx].page_data.write_at(8, &0u64.to_le_bytes());
923 let unlocked_val = Node::make_val(reader_idx, FLAG_NORMAL);
925 prev_node.next.store(unlocked_val, Ordering::Release);
926 self.head_page.store(next_idx, Ordering::Release);
928 self.reader_page.store(head_idx, Ordering::Release);
929 return Some(head_idx);
931 }
932 starnix_logging::log_warn!("swap_reader_page failed to lock, yielding");
934 retries += 1;
935 if retries >= 100_000 {
936 starnix_logging::log_error!("LocklessRingBuffer: HUNG in swap_reader_page loop");
937 return None;
938 }
939 std::hint::spin_loop();
943 }
944 }
945 pub fn read(&self, buf: &mut dyn OutputBuffer) -> Result<usize, Errno> {
946 if self.reader_active.swap(true, Ordering::AcqRel) {
949 starnix_logging::log_error!(
950 "LocklessRingBuffer: concurrent reads detected! Concurrent reads are not supported by design."
951 );
952 return error!(EBUSY);
953 }
954
955 let _lock_guard = scopeguard::guard(&self.reader_active, |reader_active| {
956 reader_active.store(false, Ordering::Release);
957 });
958
959 let mut val = self.ref_count.load(Ordering::Acquire);
964 loop {
965 if val & RING_ENABLED_BIT == 0 {
966 return error!(EAGAIN);
967 }
968 match self.ref_count.compare_exchange_weak(
969 val,
970 val + 1,
971 Ordering::Acquire,
972 Ordering::Relaxed,
973 ) {
974 Ok(_) => break,
975 Err(actual) => val = actual,
976 }
977 }
978
979 let _guard = scopeguard::guard(&self.ref_count, |ref_count| {
980 ref_count.fetch_sub(1, Ordering::Release);
981 });
982
983 if buf.available() < (*PAGE_SIZE) as usize {
984 starnix_logging::log_error!(
985 "Buffer is too small: {} bytes (needs {} bytes)",
986 buf.available(),
987 (*PAGE_SIZE) as usize
988 );
989 return error!(EINVAL);
990 }
991 if let Some(idx) = self.swap_reader_page() {
994 let node = &self.nodes[idx];
995 let mut offset = 0;
996 let page_size = (*PAGE_SIZE) as usize;
997 let bytes_written = buf.write_each(&mut move |segment| {
998 let available = page_size - offset;
999 if available == 0 {
1000 return Ok(0);
1001 }
1002 let size = std::cmp::min(segment.len(), available);
1003 let segment_mut_u8 = unsafe {
1006 std::slice::from_raw_parts_mut(segment.as_mut_ptr() as *mut u8, size)
1007 };
1008 node.page_data.read_at(offset, segment_mut_u8);
1009 offset += size;
1010 Ok(size)
1011 })?;
1012 return Ok(bytes_written);
1013 }
1014 error!(EAGAIN)
1015 }
1016
1017 pub fn size_bytes(&self) -> usize {
1018 self.nodes.len() * (*PAGE_SIZE) as usize
1019 }
1020
1021 pub fn is_enabled(&self) -> bool {
1022 self.ref_count.load(Ordering::Acquire) & RING_ENABLED_BIT != 0
1023 }
1024 pub fn disable(&self) -> Result<u64, Errno> {
1025 let _lock = self.state_mutex.lock();
1026 self.ref_count.fetch_and(!RING_ENABLED_BIT, Ordering::AcqRel);
1028 let mut yield_count: u64 = 0;
1029 loop {
1030 let active_writers = self.ref_count.load(Ordering::Acquire);
1031 if active_writers == 0 {
1032 break;
1033 }
1034 yield_count += 1;
1035 if yield_count % 1_000_000 == 0 {
1036 log_error!(
1037 "LocklessRingBuffer: disable is waiting for {active_writers} active writers",
1038 );
1039 }
1040 std::thread::yield_now();
1045 }
1046 if yield_count > 500_000 {
1047 log_warn!("LocklessRingBuffer disable took {} yields", yield_count);
1048 }
1049 if let Err(e) = self.vmo.set_size(0) {
1050 starnix_logging::log_error!(
1051 "LocklessRingBuffer disable failed to set_size(0): {:?}",
1052 e
1053 );
1054 return Err(from_status_like_fdio!(e));
1055 }
1056 let dropped = self.dropped_pages.load(Ordering::Relaxed);
1057 Ok(dropped)
1058 }
1059
1060 pub fn enable(&self) -> Result<zx::BootInstant, Errno> {
1066 let mut last_reset_guard = self.state_mutex.lock();
1067 if self.is_enabled() {
1071 return Ok(*last_reset_guard);
1072 }
1073 debug_assert_eq!(self.ref_count.load(Ordering::Relaxed), 0);
1083 let initial_pages = self.nodes.len() - 1;
1084 let capacity = self.nodes.len() * (*PAGE_SIZE) as usize;
1085 if let Err(e) = self.vmo.set_size(capacity as u64) {
1086 starnix_logging::log_error!("LocklessRingBuffer enable failed to set_size: {:?}", e);
1087 return Err(from_status_like_fdio!(e));
1088 }
1089 let now = zx::BootInstant::get();
1090 self.head_page.store(0, Ordering::Release);
1092 self.tail_page.store(self.tail_page.load(Ordering::Relaxed).next(0), Ordering::Relaxed);
1108
1109 self.reader_page.store(initial_pages, Ordering::Release);
1110
1111 self.dropped_pages.store(0, Ordering::Release);
1112 self.prev_timestamp.store(now.into_nanos() as u64, Ordering::Release);
1113 *last_reset_guard = now;
1114
1115 for i in 0..self.nodes.len() {
1116 self.nodes[i]
1118 .write_offset
1119 .store(LocklessRingBuffer::PAGE_HEADER_SIZE, Ordering::Relaxed);
1120 self.nodes[i].active_writers.store(PAGE_ACTIVE_BIT, Ordering::Release);
1121 }
1122 for i in 0..initial_pages {
1123 let next_idx = (i + 1) % initial_pages;
1124 let prev_idx = (i + initial_pages - 1) % initial_pages;
1125 self.nodes[i].next.store(Node::make_val(next_idx, FLAG_NORMAL), Ordering::Relaxed);
1126 self.nodes[i].prev.store(Node::make_val(prev_idx, FLAG_NORMAL), Ordering::Relaxed);
1127 }
1128 self.nodes[initial_pages - 1].next.store(Node::make_val(0, FLAG_HEADER), Ordering::Relaxed);
1129
1130 self.nodes[initial_pages].page_data.write_at(8, &0u64.to_le_bytes());
1132 let _ = self.vmo.op_range(zx::VmoOp::ZERO, 0, *PAGE_SIZE);
1134 let nanos = now.into_nanos() as u64;
1135 self.nodes[0].page_data.write_at(0, &nanos.to_le_bytes());
1136
1137 self.ref_count.store(RING_ENABLED_BIT, Ordering::Release);
1139 Ok(now)
1140 }
1141}
1142impl Drop for LocklessRingBuffer {
1143 fn drop(&mut self) {
1144 let (ptr, len) = self.mapping.as_ptr_len();
1145 unsafe {
1148 let _ = vmar_root_self().unmap(ptr as usize, len);
1149 }
1150 }
1151}
1152
1153#[cfg(test)]
1154mod tests {
1155 use super::*;
1156 use std::sync::Arc;
1157
1158 struct VecOutputBuffer {
1161 data: Vec<u8>,
1162 capacity: usize,
1163 }
1164 impl VecOutputBuffer {
1165 fn new(capacity: usize) -> Self {
1166 Self { data: Vec::with_capacity(capacity), capacity }
1167 }
1168 fn data(&self) -> &[u8] {
1169 &self.data
1170 }
1171 fn reset(&mut self) {
1172 self.data.clear();
1173 }
1174 }
1175 impl OutputBuffer for VecOutputBuffer {
1176 fn available(&self) -> usize {
1177 self.capacity - self.data.len()
1178 }
1179 fn write_each(&mut self, callback: &mut OutputBufferCallback<'_>) -> Result<usize, Errno> {
1180 let mut segment = vec![MaybeUninit::new(0u8); self.available()];
1181 let written = callback(&mut segment)?;
1182 self.data.extend(segment[..written].iter().map(|b| unsafe { b.assume_init() }));
1184 Ok(written)
1185 }
1186 }
1187 #[repr(C)]
1188 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
1189 struct TestMessage {
1190 thread_index: u32,
1191 timestamp_nanos: u64,
1192 delta: u64,
1193 data: [u8; 12],
1194 }
1195 fn read_data_size(page: &[u8]) -> usize {
1197 const DATA_SIZE_OFFSET: usize = 8;
1198 assert!(page.len() >= LocklessRingBuffer::PAGE_HEADER_SIZE, "page shorter than header");
1199 let mut size_bytes = [0u8; 8];
1200 size_bytes.copy_from_slice(&page[DATA_SIZE_OFFSET..LocklessRingBuffer::PAGE_HEADER_SIZE]);
1201 u64::from_le_bytes(size_bytes) as usize
1202 }
1203
1204 impl TestMessage {
1205 pub const SIZE: usize = 32;
1206 fn to_bytes(&self) -> [u8; TestMessage::SIZE] {
1207 let mut bytes = [0u8; TestMessage::SIZE];
1208 bytes[0..4].copy_from_slice(&self.thread_index.to_le_bytes());
1209 bytes[4..12].copy_from_slice(&self.timestamp_nanos.to_le_bytes());
1210 bytes[12..20].copy_from_slice(&self.delta.to_le_bytes());
1211 bytes[20..32].copy_from_slice(&self.data);
1212 bytes
1213 }
1214 fn from_bytes(bytes: &[u8]) -> Self {
1215 let mut thread_index = [0u8; 4];
1216 thread_index.copy_from_slice(&bytes[0..4]);
1217 let mut timestamp_nanos = [0u8; 8];
1218 timestamp_nanos.copy_from_slice(&bytes[4..12]);
1219 let mut delta = [0u8; 8];
1220 delta.copy_from_slice(&bytes[12..20]);
1221 let mut data = [0u8; 12];
1222 data.copy_from_slice(&bytes[20..32]);
1223 Self {
1224 thread_index: u32::from_le_bytes(thread_index),
1225 timestamp_nanos: u64::from_le_bytes(timestamp_nanos),
1226 delta: u64::from_le_bytes(delta),
1227 data,
1228 }
1229 }
1230 }
1231 #[test]
1232 fn test_init() {
1233 let buffer =
1234 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1235 .unwrap();
1236 assert_eq!(buffer.size_bytes(), 3 * (*PAGE_SIZE) as usize);
1237 }
1238 #[test]
1239 fn test_reserve() {
1240 let buffer =
1241 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1242 .unwrap();
1243 let res = buffer.reserve(100).unwrap();
1244 assert_eq!(res.0.size, 100);
1245 assert_eq!(res.0.offset, LocklessRingBuffer::PAGE_HEADER_SIZE);
1246 assert_eq!(res.0.node_idx, 0);
1247 }
1248 #[test]
1249 fn test_commit() {
1250 let buffer =
1251 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1252 .unwrap();
1253 let res = buffer.reserve(100).unwrap();
1254 buffer.commit(res.0);
1255 assert_eq!(buffer.nodes[0].active_writers.load(Ordering::Relaxed), PAGE_ACTIVE_BIT);
1256 }
1257 #[test]
1258 fn test_swap_reader_page_empty() {
1259 let buffer =
1260 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1261 .unwrap();
1262 assert_eq!(buffer.swap_reader_page(), None);
1263 }
1264 #[test]
1265 fn test_swap_reader_page_success() {
1266 let buffer =
1267 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1268 .unwrap();
1269 let res1 =
1271 buffer.reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE).unwrap();
1272 buffer.commit(res1.0);
1273 let res2 = buffer.reserve(100).unwrap();
1275 assert_eq!(res2.0.node_idx, 1);
1276 buffer.commit(res2.0);
1277 let old_head = buffer.swap_reader_page();
1280 assert_eq!(old_head, Some(0));
1281 assert_eq!(buffer.head_page.load(Ordering::Relaxed), 1);
1283 }
1284 #[test]
1285 fn test_concurrent_reserve() {
1286 use std::sync::Arc;
1287 let buffer = Arc::new(
1288 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1289 .unwrap(),
1290 );
1291 let mut handles = vec![];
1292 for _ in 0..5 {
1293 let buffer_clone = Arc::clone(&buffer);
1294 let handle = std::thread::spawn(move || {
1295 for _ in 0..20 {
1296 if let Ok(res) = buffer_clone.reserve(10) {
1297 buffer_clone.commit(res.0);
1298 }
1299 }
1300 });
1301 handles.push(handle);
1302 }
1303 for handle in handles {
1304 handle.join().unwrap();
1305 }
1306 for i in 0..buffer.nodes.len() {
1307 assert_eq!(buffer.nodes[i].active_writers.load(Ordering::Relaxed), PAGE_ACTIVE_BIT);
1308 }
1309 }
1310 #[test]
1311 fn test_reserve_moves_to_next_page() {
1312 let buffer =
1313 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1314 .unwrap();
1315 let res1 = buffer
1317 .reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE - 50)
1318 .unwrap();
1319 buffer.commit(res1.0);
1320 let res2 = buffer.reserve(100).unwrap();
1322 assert_eq!(res2.0.node_idx, 1);
1324 assert_eq!(res2.0.offset, (*PAGE_SIZE) as usize + LocklessRingBuffer::PAGE_HEADER_SIZE);
1325 }
1326 #[test]
1327 fn test_reserve_overwrite() {
1328 let buffer =
1329 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
1330 .unwrap();
1331 let res1 =
1333 buffer.reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE).unwrap();
1334 buffer.commit(res1.0);
1335 let _res2 =
1337 buffer.reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE).unwrap();
1338 let res3 = buffer.reserve(100).unwrap();
1342 assert_eq!(res3.0.node_idx, 0);
1344 assert_eq!(buffer.dropped_pages(), 1);
1345 assert_eq!(buffer.head_page.load(Ordering::Relaxed), 1);
1347 }
1348 #[test]
1349 fn test_read() {
1350 let buffer =
1351 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1352 .unwrap();
1353 let res1 =
1354 buffer.reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE).unwrap();
1355 let data = vec![1u8; (*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE];
1356 res1.0.write_at(0, &data);
1357 buffer.commit(res1.0);
1358 let res2 = buffer.reserve(100).unwrap();
1360 buffer.commit(res2.0);
1361 let mut dest = VecOutputBuffer::new((*PAGE_SIZE) as usize);
1362 let result = buffer.read(&mut dest);
1363 assert!(result.is_ok());
1364 assert_eq!(result.unwrap(), (*PAGE_SIZE) as usize);
1365 assert_eq!(&dest.data()[LocklessRingBuffer::PAGE_HEADER_SIZE..], &data[..]);
1366 }
1367 #[test]
1368 fn test_enable_disable() {
1369 let buffer =
1370 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1371 .unwrap();
1372 assert!(buffer.disable().is_ok());
1373 assert_eq!(buffer.ref_count.load(Ordering::Relaxed) & RING_ENABLED_BIT, 0);
1374 let res = buffer.reserve(100);
1375 assert_eq!(res.unwrap_err(), starnix_uapi::errno!(ENOMEM));
1376 assert!(buffer.enable().is_ok());
1377 assert_eq!(buffer.size_bytes(), 3 * (*PAGE_SIZE) as usize);
1378 let res = buffer.reserve(100);
1379 assert!(res.is_ok());
1380 }
1381
1382 #[test]
1385 fn test_enable_after_disable_restores_requested_size() {
1386 let size_bytes = 512 * (*PAGE_SIZE) as usize;
1387 let buffer = LocklessRingBuffer::new(size_bytes, true, fuchsia_trace::Id::new()).unwrap();
1388 assert_eq!(buffer.size_bytes(), size_bytes);
1389
1390 buffer.disable().unwrap();
1391 assert_eq!(buffer.vmo.get_size().unwrap(), 0);
1392
1393 buffer.enable().unwrap();
1394 assert_eq!(buffer.size_bytes(), size_bytes);
1395 assert_eq!(buffer.vmo.get_size().unwrap(), size_bytes as u64);
1396 }
1397 fn start_reader_thread(
1399 buffer_reader: Arc<LocklessRingBuffer>,
1400 writers_done_reader: Arc<std::sync::atomic::AtomicBool>,
1401 ) -> std::thread::JoinHandle<Vec<TestMessage>> {
1402 start_gated_reader_thread(buffer_reader, writers_done_reader, false)
1403 }
1404
1405 fn start_gated_reader_thread(
1408 buffer_reader: Arc<LocklessRingBuffer>,
1409 writers_done_reader: Arc<std::sync::atomic::AtomicBool>,
1410 wait_for_dropped_pages: bool,
1411 ) -> std::thread::JoinHandle<Vec<TestMessage>> {
1412 std::thread::spawn(move || {
1413 if wait_for_dropped_pages {
1414 while buffer_reader.dropped_pages() == 0
1415 && !writers_done_reader.load(Ordering::Acquire)
1416 {
1417 std::thread::yield_now();
1418 }
1419 }
1420 let mut all_messages = Vec::new();
1421 let mut dest = VecOutputBuffer::new((*PAGE_SIZE) as usize);
1422 let mut consecutive_eagain = 0;
1423 loop {
1424 dest.reset();
1425 match buffer_reader.read(&mut dest) {
1426 Ok(bytes_read) => {
1427 consecutive_eagain = 0;
1428 assert_eq!(bytes_read, (*PAGE_SIZE) as usize);
1429 let mut header_ts_bytes = [0u8; 8];
1430 header_ts_bytes.copy_from_slice(&dest.data()[0..8]);
1431 let header_timestamp = u64::from_le_bytes(header_ts_bytes);
1432 let data_size = read_data_size(dest.data());
1433 let max_offset = LocklessRingBuffer::PAGE_HEADER_SIZE + data_size;
1434 let mut offset = LocklessRingBuffer::PAGE_HEADER_SIZE;
1435 let mut first_msg = true;
1436 while offset + TestMessage::SIZE <= max_offset {
1437 let msg_bytes = &dest.data()[offset..offset + TestMessage::SIZE];
1438 let msg = TestMessage::from_bytes(msg_bytes);
1439 if first_msg {
1440 if header_timestamp != msg.timestamp_nanos {
1441 println!(
1442 "HEADER TIMESTAMP MISMATCH: header={}, msg={}",
1443 header_timestamp, msg.timestamp_nanos
1444 );
1445 }
1446 first_msg = false;
1447 }
1448 all_messages.push(msg);
1449 offset += TestMessage::SIZE;
1450 }
1451 }
1452 Err(e) if e == starnix_uapi::errno!(EAGAIN) => {
1453 if writers_done_reader.load(Ordering::Acquire) {
1454 let head_val = buffer_reader.head_page.load(Ordering::Relaxed);
1455 let tail_val = buffer_reader.tail_page.load(Ordering::Relaxed);
1456 if Node::get_index(head_val) == tail_val.index() as usize {
1459 break;
1460 }
1461 consecutive_eagain += 1;
1462 assert!(
1463 consecutive_eagain < 500,
1464 "LocklessRingBuffer reader stuck: consecutive EAGAIN limit exceeded (5s) after writers finished. head_page={}, tail_page={}, reader_page={}",
1465 Node::get_index(head_val),
1466 tail_val.index(),
1467 buffer_reader.reader_page.load(Ordering::Relaxed)
1468 );
1469 }
1470 std::thread::sleep(std::time::Duration::from_millis(10));
1471 }
1472 Err(e) => panic!("Unexpected error from read: {:?}", e),
1473 }
1474 }
1475 all_messages
1476 })
1477 }
1478 fn check_all_message_data(all_messages: &[TestMessage], num_threads: u32) {
1479 let mut prev_timestamp = 0;
1480 let mut thread_counts = vec![0; num_threads as usize];
1481 let mut out_of_order = 0;
1482 let mut corrupted = 0;
1483 for msg in all_messages {
1484 if msg.timestamp_nanos < prev_timestamp {
1485 println!("OUT OF ORDER: prev_timestamp={}, current={:?}", prev_timestamp, msg);
1486 out_of_order += 1;
1487 }
1488 prev_timestamp = msg.timestamp_nanos;
1489 if &msg.data != b"Event data\0\0" || msg.thread_index >= num_threads {
1490 println!("CORRUPTED: msg={:?}", msg);
1491 corrupted += 1;
1492 } else {
1493 thread_counts[msg.thread_index as usize] += 1;
1494 }
1495 }
1496 println!(
1497 "TEST_RESULT: Read {} messages, {} out of order, {} corrupted. Thread counts: {:?}",
1498 all_messages.len(),
1499 out_of_order,
1500 corrupted,
1501 thread_counts
1502 );
1503 assert_eq!(corrupted, 0, "Found corrupted messages");
1505 }
1506 #[test]
1507 fn test_concurrent_read_write_4() {
1508 let num_threads = 4;
1509 let msgs_per_thread = 64;
1510 let buffer = Arc::new(
1514 LocklessRingBuffer::new(4 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1515 .unwrap(),
1516 );
1517 let writers_done = Arc::new(std::sync::atomic::AtomicBool::new(false));
1518 let mut handles = vec![];
1519 let buffer_reader = Arc::clone(&buffer);
1521 let writers_done_reader = Arc::clone(&writers_done);
1522 let reader_handle = start_reader_thread(buffer_reader, writers_done_reader);
1523 for thread_index in 0..num_threads {
1525 let buffer_clone = Arc::clone(&buffer);
1526 let handle = std::thread::spawn(move || {
1527 std::thread::sleep(std::time::Duration::from_millis(10 + thread_index as u64));
1528 for _ in 0..msgs_per_thread {
1529 let (res, now, delta) = buffer_clone.reserve(TestMessage::SIZE).unwrap();
1531 let msg = TestMessage {
1532 thread_index,
1533 timestamp_nanos: now.into_nanos() as u64,
1534 delta: delta.into_nanos() as u64,
1535 data: *b"Event data\0\0",
1536 };
1537 res.write_at(0, &msg.to_bytes());
1538 buffer_clone.commit(res);
1539 std::thread::sleep(std::time::Duration::from_nanos(thread_index as u64));
1540 }
1541 });
1542 handles.push(handle);
1543 }
1544 for handle in handles {
1545 handle.join().unwrap();
1546 }
1547 writers_done.store(true, Ordering::Release);
1548 let all_messages = reader_handle.join().unwrap();
1549 check_all_message_data(&all_messages, num_threads);
1551 assert!(
1553 all_messages.len() >= 250 && all_messages.len() <= 256,
1554 "Expected between 250 and 256 messages, got {}",
1555 all_messages.len()
1556 );
1557 }
1558 #[test]
1559 fn test_concurrent_read_write_1_thread() {
1560 let num_threads = 1;
1561 let msgs_per_thread = 256;
1562 let buffer = Arc::new(
1566 LocklessRingBuffer::new(5 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
1567 .unwrap(),
1568 );
1569 let writers_done = Arc::new(std::sync::atomic::AtomicBool::new(false));
1570 let mut handles = vec![];
1571 let buffer_reader = Arc::clone(&buffer);
1573 let writers_done_reader = Arc::clone(&writers_done);
1574 let reader_handle = start_reader_thread(buffer_reader, writers_done_reader);
1575 for thread_index in 0..num_threads {
1577 let buffer_clone = Arc::clone(&buffer);
1578 let handle = std::thread::spawn(move || {
1579 std::thread::sleep(std::time::Duration::from_millis(10 + thread_index as u64));
1580 for _ in 0..msgs_per_thread {
1581 let (res, now, delta) = buffer_clone.reserve(TestMessage::SIZE).unwrap();
1582 let msg = TestMessage {
1583 thread_index,
1584 timestamp_nanos: now.into_nanos() as u64,
1585 delta: delta.into_nanos() as u64,
1586 data: *b"Event data\0\0",
1587 };
1588 res.write_at(0, &msg.to_bytes());
1589 buffer_clone.commit(res);
1590 std::thread::sleep(std::time::Duration::from_nanos(thread_index as u64));
1591 }
1592 });
1593 handles.push(handle);
1594 }
1595 for handle in handles {
1596 handle.join().unwrap();
1597 }
1598 writers_done.store(true, Ordering::Release);
1599 let all_messages = reader_handle.join().unwrap();
1600 check_all_message_data(&all_messages, num_threads);
1601 assert_eq!(
1602 all_messages.len(),
1603 254,
1604 "Expected exactly 254 messages, got {}",
1605 all_messages.len()
1606 );
1607 }
1608 #[test]
1609 fn test_concurrent_read_write_8_threads() {
1610 let num_threads = 8;
1611 let msgs_per_thread = 128;
1612 let buffer = Arc::new(
1617 LocklessRingBuffer::new(12 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1618 .unwrap(),
1619 );
1620 let writers_done = Arc::new(std::sync::atomic::AtomicBool::new(false));
1621 let _writers_done_guard = scopeguard::guard(Arc::clone(&writers_done), |done| {
1622 done.store(true, Ordering::Release);
1623 });
1624 let mut handles = vec![];
1625 let buffer_reader = Arc::clone(&buffer);
1627 let writers_done_reader = Arc::clone(&writers_done);
1628 let reader_handle = start_reader_thread(buffer_reader, writers_done_reader);
1629 for thread_index in 0..num_threads {
1631 let buffer_clone = Arc::clone(&buffer);
1632 let handle = std::thread::spawn(move || {
1633 std::thread::sleep(std::time::Duration::from_millis(10 + thread_index as u64));
1634 for _ in 0..msgs_per_thread {
1635 let mut retries = 0;
1637 let (res, now, delta) = loop {
1638 match buffer_clone.reserve(TestMessage::SIZE) {
1639 Ok(r) => break r,
1640 Err(e) if e == starnix_uapi::errno!(ENOSPC) => {
1641 retries += 1;
1642 assert!(
1643 retries < 100,
1644 "LocklessRingBuffer: ENOSPC transient limit exceeded (100ms). Reader may have hung."
1645 );
1646 std::thread::sleep(std::time::Duration::from_millis(1));
1650 }
1651 Err(e) => panic!("Unexpected error: {:?}", e),
1652 }
1653 };
1654 let msg = TestMessage {
1655 thread_index,
1656 timestamp_nanos: now.into_nanos() as u64,
1657 delta: delta.into_nanos() as u64,
1658 data: *b"Event data\0\0",
1659 };
1660 res.write_at(0, &msg.to_bytes());
1661 buffer_clone.commit(res);
1662 std::thread::sleep(std::time::Duration::from_nanos(thread_index as u64));
1663 }
1664 });
1665 handles.push(handle);
1666 }
1667 for handle in handles {
1668 handle.join().unwrap();
1669 }
1670
1671 writers_done.store(true, Ordering::Release);
1672 let all_messages = reader_handle.join().unwrap();
1673 check_all_message_data(&all_messages, num_threads);
1675 assert!(
1681 all_messages.len() >= 1008 && all_messages.len() <= 1024,
1682 "Expected between 1008 and 1024 messages, got {}",
1683 all_messages.len()
1684 );
1685 }
1686 #[test]
1687 fn test_disable_waits_for_ref_count() {
1688 use std::sync::Arc;
1689 use std::sync::atomic::{AtomicBool, Ordering};
1690 let buffer = Arc::new(
1691 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
1692 .unwrap(),
1693 );
1694 let res = buffer.reserve(100).unwrap();
1696 let disable_finished = Arc::new(AtomicBool::new(false));
1697 let disable_finished_clone = Arc::clone(&disable_finished);
1698 let buffer_clone = Arc::clone(&buffer);
1699 let handle = std::thread::spawn(move || {
1701 buffer_clone.disable().unwrap();
1702 disable_finished_clone.store(true, Ordering::Relaxed);
1703 });
1704 while buffer.ref_count.load(Ordering::Acquire) & RING_ENABLED_BIT != 0 {
1706 std::hint::spin_loop();
1707 }
1708 assert!(!disable_finished.load(Ordering::Relaxed));
1710 let res2 = buffer.reserve(100);
1712 assert_eq!(res2.unwrap_err(), starnix_uapi::errno!(ENOMEM));
1713 let data = vec![42u8; 100];
1716 res.0.write_at(0, &data);
1717 buffer.commit(res.0);
1718 handle.join().unwrap();
1720 assert!(disable_finished.load(Ordering::Relaxed));
1722 }
1723 #[test]
1724 fn test_reservation_drop_cancels_writer_count() {
1725 let buffer =
1726 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
1727 .unwrap();
1728 let res = buffer.reserve(100).unwrap();
1730 assert_eq!(buffer.ref_count.load(Ordering::Relaxed), RING_ENABLED_BIT | 1);
1731 std::mem::drop(res.0);
1733 assert_eq!(buffer.ref_count.load(Ordering::Relaxed), RING_ENABLED_BIT);
1735 let res2 = buffer.reserve(100).unwrap();
1737 buffer.commit(res2.0);
1738 }
1739 #[test]
1740 fn test_overwrite_and_read_4_pages() {
1741 let num_threads = 1;
1742 let buffer = Arc::new(
1745 LocklessRingBuffer::new(6 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
1746 .unwrap(),
1747 );
1748 let writers_done = Arc::new(std::sync::atomic::AtomicBool::new(false));
1749 let msgs_per_thread = 7 * 127;
1752 let buffer_clone = Arc::clone(&buffer);
1753 let thread_index = 0;
1754 let writer_handle = std::thread::spawn(move || {
1755 for _ in 0..msgs_per_thread {
1756 let (res, now, delta) = buffer_clone.reserve(TestMessage::SIZE).unwrap();
1757 let msg = TestMessage {
1758 thread_index,
1759 timestamp_nanos: now.into_nanos() as u64,
1760 delta: delta.into_nanos() as u64,
1761 data: *b"Event data\0\0",
1762 };
1763 res.write_at(0, &msg.to_bytes());
1764 buffer_clone.commit(res);
1765 }
1766 });
1767 writer_handle.join().unwrap();
1768 assert_eq!(buffer.dropped_pages(), 2, "Expected 2 pages to be dropped");
1769 writers_done.store(true, Ordering::Release);
1770 let buffer_reader = Arc::clone(&buffer);
1771 let writers_done_reader = Arc::clone(&writers_done);
1772 let reader_handle = start_reader_thread(buffer_reader, writers_done_reader);
1773 let all_messages = reader_handle.join().unwrap();
1774 check_all_message_data(&all_messages, num_threads);
1775 assert_eq!(all_messages.len(), 4 * 127, "Expected exactly 4 pages of messages");
1776 }
1777 #[test]
1778 fn test_reserve_full_producer_consumer() {
1779 let buffer =
1780 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1781 .unwrap();
1782 let res1 =
1784 buffer.reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE).unwrap();
1785 buffer.commit(res1.0);
1786 let res2 =
1788 buffer.reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE).unwrap();
1789 buffer.commit(res2.0);
1790 let res3 = buffer.reserve(100);
1793 assert_eq!(res3.unwrap_err(), starnix_uapi::errno!(ENOSPC));
1794 }
1795
1796 #[test]
1797 fn test_reserve_zero_size() {
1798 let buffer =
1799 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1800 .unwrap();
1801 let res = buffer.reserve(0);
1802 assert_eq!(res.unwrap_err(), starnix_uapi::errno!(EINVAL));
1803 }
1804 #[test]
1805 fn test_concurrent_overwrite_stability() {
1806 let num_threads = 4;
1807 let msgs_per_thread = 256;
1808 let buffer = Arc::new(
1810 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
1811 .unwrap(),
1812 );
1813 let max_payload = (*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE;
1817 let num_msgs = max_payload / TestMessage::SIZE;
1818 for _ in 0..(2 * num_msgs) {
1819 let (res, now, delta) = buffer.reserve(TestMessage::SIZE).unwrap();
1820 let msg = TestMessage {
1821 thread_index: 0,
1822 timestamp_nanos: now.into_nanos() as u64,
1823 delta: delta.into_nanos() as u64,
1824 data: *b"Event data\0\0",
1825 };
1826 res.write_at(0, &msg.to_bytes());
1827 buffer.commit(res);
1828 }
1829
1830 let writers_done = Arc::new(std::sync::atomic::AtomicBool::new(false));
1831 let mut handles = vec![];
1832
1833 let buffer_reader = Arc::clone(&buffer);
1834 let writers_done_reader = Arc::clone(&writers_done);
1835 let reader_handle = start_gated_reader_thread(buffer_reader, writers_done_reader, true);
1838
1839 for thread_index in 0..num_threads {
1841 let buffer_clone = Arc::clone(&buffer);
1842 let handle = std::thread::spawn(move || {
1843 for _ in 0..msgs_per_thread {
1844 if let Ok((res, now, delta)) = buffer_clone.reserve(TestMessage::SIZE) {
1845 let msg = TestMessage {
1846 thread_index,
1847 timestamp_nanos: now.into_nanos() as u64,
1848 delta: delta.into_nanos() as u64,
1849 data: *b"Event data\0\0",
1850 };
1851 res.write_at(0, &msg.to_bytes());
1852 buffer_clone.commit(res);
1853 }
1854 std::thread::yield_now();
1856 }
1857 });
1858 handles.push(handle);
1859 }
1860 for handle in handles {
1861 handle.join().unwrap();
1862 }
1863 writers_done.store(true, Ordering::Release);
1864 let all_messages = reader_handle.join().unwrap();
1865 check_all_message_data(&all_messages, num_threads);
1867 assert!(buffer.dropped_pages() > 0, "Expected at least some dropped pages");
1869 }
1870
1871 #[test]
1872 fn test_out_of_order_completion_same_page() {
1873 let buffer =
1874 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1875 .unwrap();
1876
1877 let header_size = LocklessRingBuffer::PAGE_HEADER_SIZE;
1878 let page_size = (*PAGE_SIZE) as usize;
1879 let available_space = page_size - header_size;
1880
1881 let size1 = available_space / 2;
1882 let size2 = available_space - size1;
1883
1884 let (res1, _, _) = buffer.reserve(size1).unwrap();
1886 let (res2, _, _) = buffer.reserve(size2).unwrap();
1888
1889 buffer.commit(res2);
1891
1892 assert!(buffer.swap_reader_page().is_none());
1894
1895 buffer.commit(res1);
1897
1898 let _ = buffer.reserve(10).unwrap();
1900
1901 let swapped = buffer.swap_reader_page();
1903 assert!(swapped.is_some());
1904 assert_eq!(swapped.unwrap(), 0); }
1906
1907 #[test]
1908 fn test_concurrent_readers_rejection() {
1909 #[derive(Debug)]
1910 struct SleepingOutputBuffer {
1911 barrier_in_available: Arc<std::sync::Barrier>,
1912 barrier_test_done: Arc<std::sync::Barrier>,
1913 }
1914 impl OutputBuffer for SleepingOutputBuffer {
1915 fn write_each(
1916 &mut self,
1917 _callback: &mut OutputBufferCallback<'_>,
1918 ) -> Result<usize, Errno> {
1919 Ok(0)
1920 }
1921 fn available(&self) -> usize {
1922 self.barrier_in_available.wait();
1925
1926 self.barrier_test_done.wait();
1928
1929 (*PAGE_SIZE) as usize
1930 }
1931 }
1932
1933 let buffer = Arc::new(
1934 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
1935 .unwrap(),
1936 );
1937
1938 let barrier_in_available = Arc::new(std::sync::Barrier::new(2));
1939 let barrier_test_done = Arc::new(std::sync::Barrier::new(2));
1940
1941 let buffer_clone = Arc::clone(&buffer);
1942 let barrier_in_available_clone = Arc::clone(&barrier_in_available);
1943 let barrier_test_done_clone = Arc::clone(&barrier_test_done);
1944 let handle = std::thread::spawn(move || {
1945 let mut dest = SleepingOutputBuffer {
1946 barrier_in_available: barrier_in_available_clone,
1947 barrier_test_done: barrier_test_done_clone,
1948 };
1949 let _ = buffer_clone.read(&mut dest);
1950 });
1951
1952 barrier_in_available.wait();
1954
1955 let mut dest = VecOutputBuffer::new((*PAGE_SIZE) as usize);
1957 let res = buffer.read(&mut dest);
1958 assert_eq!(res.unwrap_err(), starnix_uapi::errno!(EBUSY));
1959
1960 barrier_test_done.wait();
1962
1963 handle.join().unwrap();
1964 }
1965
1966 #[test]
1967 fn test_stale_offset_livelock() {
1968 let num_threads = 8;
1969 let msgs_per_thread = 200;
1970 let buffer = Arc::new(
1972 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
1973 .unwrap(),
1974 );
1975 let mut handles = vec![];
1976
1977 for thread_index in 0..num_threads {
1978 let buffer_clone = Arc::clone(&buffer);
1979 let handle = std::thread::spawn(move || {
1980 for _ in 0..msgs_per_thread {
1981 if let Ok((res, now, delta)) = buffer_clone.reserve(TestMessage::SIZE) {
1982 let msg = TestMessage {
1983 thread_index,
1984 timestamp_nanos: now.into_nanos() as u64,
1985 delta: delta.into_nanos() as u64,
1986 data: *b"Event data\0\0",
1987 };
1988 res.write_at(0, &msg.to_bytes());
1989 buffer_clone.commit(res);
1990 }
1991 std::thread::yield_now();
1992 }
1993 });
1994 handles.push(handle);
1995 }
1996
1997 for handle in handles {
1998 handle.join().unwrap();
1999 }
2000
2001 let dropped = buffer.dropped_pages();
2002 println!("High contention completed. Total dropped pages = {}", dropped);
2003 assert!(dropped > 0);
2005 }
2006
2007 #[test]
2008 fn test_writer_preemption_and_overwrite_prevention() {
2009 let buffer = Arc::new(
2010 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2011 .unwrap(),
2012 );
2013
2014 let res1 = buffer.reserve(100).unwrap();
2016 assert_eq!(res1.0.node_idx, 0);
2017
2018 let buffer_clone = Arc::clone(&buffer);
2021 let writer_finished = Arc::new(std::sync::atomic::AtomicBool::new(false));
2022 let writer_finished_clone = Arc::clone(&writer_finished);
2023
2024 let handle = std::thread::spawn(move || {
2025 let res2 = buffer_clone
2027 .reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE - 100)
2028 .unwrap();
2029 buffer_clone.commit(res2.0);
2030
2031 let res3 = buffer_clone
2033 .reserve((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE)
2034 .unwrap();
2035 buffer_clone.commit(res3.0);
2036
2037 let res4 = buffer_clone.reserve(100).unwrap();
2040 buffer_clone.commit(res4.0);
2041
2042 writer_finished_clone.store(true, Ordering::Release);
2043 });
2044
2045 std::thread::sleep(std::time::Duration::from_millis(50));
2047 assert!(!writer_finished.load(Ordering::Acquire));
2048
2049 buffer.commit(res1.0);
2051
2052 handle.join().unwrap();
2053 assert!(writer_finished.load(Ordering::Acquire));
2054 }
2055
2056 #[test]
2057 fn test_extreme_disable_enable_stress() {
2058 let num_threads = 8;
2059 let msgs_per_thread = 100;
2060 let buffer = Arc::new(
2061 LocklessRingBuffer::new(5 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2062 .unwrap(),
2063 );
2064 let mut handles = vec![];
2065
2066 for thread_index in 0..num_threads {
2068 let buffer_clone = Arc::clone(&buffer);
2069 let handle = std::thread::spawn(move || {
2070 let mut count = 0;
2071 while count < msgs_per_thread {
2072 match buffer_clone.reserve(TestMessage::SIZE) {
2073 Ok((res, now, delta)) => {
2074 let msg = TestMessage {
2075 thread_index,
2076 timestamp_nanos: now.into_nanos() as u64,
2077 delta: delta.into_nanos() as u64,
2078 data: *b"Event data\0\0",
2079 };
2080 res.write_at(0, &msg.to_bytes());
2081 buffer_clone.commit(res);
2082 count += 1;
2083 }
2084 Err(e) if e == starnix_uapi::errno!(ENOMEM) => {
2085 std::thread::yield_now();
2087 }
2088 Err(e) => panic!("Unexpected error during reserve: {:?}", e),
2089 }
2090 }
2091 count
2092 });
2093 handles.push(handle);
2094 }
2095
2096 let buffer_clone = Arc::clone(&buffer);
2098 let coordinator = std::thread::spawn(move || {
2099 for _ in 0..20 {
2100 std::thread::sleep(std::time::Duration::from_millis(5));
2101 let _dropped = buffer_clone.disable().unwrap();
2103
2104 let res = buffer_clone.reserve(TestMessage::SIZE);
2106 assert_eq!(res.unwrap_err(), starnix_uapi::errno!(ENOMEM));
2107
2108 std::thread::sleep(std::time::Duration::from_millis(2));
2109 let _now = buffer_clone.enable().unwrap();
2111 }
2112 });
2113
2114 coordinator.join().unwrap();
2115 let mut total_writes = 0;
2116 for handle in handles {
2117 total_writes += handle.join().unwrap();
2118 }
2119 println!("Disable/enable stress completed. Total writes = {}", total_writes);
2120 assert_eq!(total_writes, num_threads * msgs_per_thread);
2121 }
2122
2123 #[test]
2124 fn test_reader_loop_swapping_high_contention() {
2125 let num_threads = 8usize;
2126 let msgs_per_thread = 100;
2127 let buffer = Arc::new(
2131 LocklessRingBuffer::new(10 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2132 .unwrap(),
2133 );
2134 let writers_done = Arc::new(std::sync::atomic::AtomicBool::new(false));
2135 let mut handles = vec![];
2136
2137 let buffer_reader = Arc::clone(&buffer);
2139 let writers_done_reader = Arc::clone(&writers_done);
2140 let reader_handle = start_reader_thread(buffer_reader, writers_done_reader);
2141
2142 for thread_index in 0..num_threads {
2144 let buffer_clone = Arc::clone(&buffer);
2145 let handle = std::thread::spawn(move || {
2146 for _ in 0..msgs_per_thread {
2147 let (res, now, delta) = buffer_clone.reserve(TestMessage::SIZE).unwrap();
2148 let msg = TestMessage {
2149 thread_index: thread_index as u32,
2150 timestamp_nanos: now.into_nanos() as u64,
2151 delta: delta.into_nanos() as u64,
2152 data: *b"Event data\0\0",
2153 };
2154 res.write_at(0, &msg.to_bytes());
2155 buffer_clone.commit(res);
2156 std::thread::yield_now();
2158 }
2159 });
2160 handles.push(handle);
2161 }
2162
2163 for handle in handles {
2164 handle.join().unwrap();
2165 }
2166 writers_done.store(true, Ordering::Release);
2167 let all_messages = reader_handle.join().unwrap();
2168 let messages_read = all_messages.len();
2169 check_all_message_data(&all_messages, num_threads as u32);
2170 let dropped = buffer.dropped_pages();
2171 println!(
2172 "Reader high contention completed. Messages read = {}, dropped pages = {}",
2173 messages_read, dropped
2174 );
2175 let messages_per_page =
2177 ((*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE) / TestMessage::SIZE;
2178 let total_written = num_threads * msgs_per_thread;
2179 let total_accounted = messages_read + (dropped as usize * messages_per_page);
2180 assert!(total_accounted >= total_written - messages_per_page);
2181 }
2182
2183 #[test]
2184 fn test_failed_reservation_offset_boundary() {
2185 let buffer =
2186 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2187 .unwrap();
2188 let page_size = (*PAGE_SIZE) as usize;
2189 let max_payload = page_size - LocklessRingBuffer::PAGE_HEADER_SIZE;
2190
2191 let msg_size = 100;
2193 let num_msgs = max_payload / msg_size;
2194 for _ in 0..num_msgs {
2195 let (res, _, _) = buffer.reserve(msg_size).unwrap();
2196 buffer.commit(res);
2197 }
2198
2199 let expected_offset = LocklessRingBuffer::PAGE_HEADER_SIZE + num_msgs * msg_size;
2201 assert_eq!(buffer.nodes[0].write_offset.load(Ordering::Relaxed), expected_offset);
2202
2203 let (res, _, _) = buffer.reserve(msg_size).unwrap();
2205 assert_eq!(res.node_idx, 1);
2206 buffer.commit(res);
2207
2208 assert_eq!(
2211 buffer.nodes[0].write_offset.load(Ordering::Relaxed),
2212 expected_offset | WRITE_OFFSET_SEALED_BIT
2213 );
2214 }
2215
2216 #[test]
2217 fn test_sealed_write_offset_prevents_late_small_reservation() {
2218 let buffer =
2219 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
2220 .unwrap();
2221 let page_size = (*PAGE_SIZE) as usize;
2222 let header_size = LocklessRingBuffer::PAGE_HEADER_SIZE;
2223
2224 let initial_size = page_size - header_size - 80;
2226 let (res0, _, _) = buffer.reserve(initial_size).unwrap();
2227 res0.write_at(0, &vec![0xAA; initial_size]);
2228 buffer.commit(res0);
2229
2230 buffer.nodes[0].active_writers.fetch_add(1, Ordering::Relaxed);
2235 buffer.nodes[0].finalize();
2236
2237 assert!(buffer.try_reserve_on_page(&buffer.nodes[0], 32).is_err());
2241 assert_eq!(
2243 buffer.nodes[0].write_offset.load(Ordering::Relaxed),
2244 (header_size + initial_size) | WRITE_OFFSET_SEALED_BIT
2245 );
2246 buffer.nodes[0].release_writer();
2247
2248 let (res_small, _, _) = buffer.reserve(32).unwrap();
2250 assert_eq!(res_small.node_idx, 1);
2251 buffer.commit(res_small);
2252
2253 let mut dest = VecOutputBuffer::new(page_size);
2254 let bytes_read = buffer.read(&mut dest).unwrap();
2255 assert_eq!(bytes_read, page_size);
2256 assert_eq!(read_data_size(dest.data()), initial_size);
2257 }
2258
2259 #[test]
2260 fn test_advance_reopens_page_sealed_while_empty() {
2261 let buffer =
2263 LocklessRingBuffer::new(4 * (*PAGE_SIZE) as usize, false, fuchsia_trace::Id::new())
2264 .unwrap();
2265 let page_size = (*PAGE_SIZE) as usize;
2266 let header_size = LocklessRingBuffer::PAGE_HEADER_SIZE;
2267 let record_size = page_size - header_size;
2268
2269 buffer.nodes[1].finalize();
2273 assert_eq!(
2274 buffer.nodes[1].write_offset.load(Ordering::Relaxed),
2275 header_size | WRITE_OFFSET_SEALED_BIT
2276 );
2277 assert_eq!(
2278 buffer.nodes[1].active_writers.load(Ordering::Relaxed) & FLAGS_MASK,
2279 PAGE_FINALIZED_BIT
2280 );
2281
2282 let (res0, _, _) = buffer.reserve(record_size).unwrap();
2284 assert_eq!(res0.node_idx, 0);
2285 res0.write_at(0, &vec![0xAA; record_size]);
2286 buffer.commit(res0);
2287
2288 let (res1, _, _) = buffer.reserve(record_size).unwrap();
2291 assert_eq!(res1.node_idx, 1);
2292 assert_eq!(buffer.tail_page.load(Ordering::Relaxed).index(), 1);
2293 assert_eq!(
2294 buffer.nodes[1].active_writers.load(Ordering::Relaxed),
2295 PAGE_ACTIVE_BIT | 1,
2296 "Node 1 must be active again, with the one writer registered"
2297 );
2298 res1.write_at(0, &vec![0xBB; record_size]);
2299 buffer.commit(res1);
2300
2301 let (res2, _, _) = buffer.reserve(32).unwrap();
2303 assert_eq!(res2.node_idx, 2);
2304 buffer.commit(res2);
2305
2306 for fill in [0xAA, 0xBB] {
2308 let mut dest = VecOutputBuffer::new(page_size);
2309 assert_eq!(buffer.read(&mut dest).unwrap(), page_size);
2310 assert_eq!(read_data_size(dest.data()), record_size);
2311 assert!(dest.data()[header_size..].iter().all(|&b| b == fill));
2312 }
2313 }
2314
2315 #[test]
2316 fn test_stale_tail_advance_across_wraparound() {
2317 let buffer =
2318 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2319 .unwrap();
2320 let max_payload = (*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE;
2321
2322 let stale_tail_val = buffer.tail_page.load(Ordering::Acquire);
2324 assert_eq!(stale_tail_val, TailVal::INITIAL);
2325
2326 let (res0, _, _) = buffer.reserve(max_payload).unwrap();
2328 buffer.commit(res0);
2329 let (res1, _, _) = buffer.reserve(max_payload).unwrap();
2330 assert_eq!(res1.node_idx, 1);
2331 buffer.commit(res1);
2332
2333 let (res2, _, _) = buffer.reserve(100).unwrap();
2336 assert_eq!(res2.node_idx, 0);
2337 buffer.commit(res2);
2338
2339 let current_tail_val = buffer.tail_page.load(Ordering::Acquire);
2342 assert_eq!(current_tail_val.index(), 0);
2343 assert_eq!(current_tail_val.generation(), 2);
2344 assert_eq!(buffer.head_page.load(Ordering::Acquire), 1);
2345 assert_eq!(buffer.dropped_pages(), 1);
2346
2347 let mut yield_tracker = YieldTracker::default();
2353 let res = buffer.advance_to_next_page(&buffer.nodes[0], stale_tail_val, &mut yield_tracker);
2354 assert_eq!(res, AdvanceResult::Advanced);
2355 assert_eq!(
2356 buffer.tail_page.load(Ordering::Acquire),
2357 current_tail_val,
2358 "Stale advance_to_next_page must not advance tail_page on a later generation"
2359 );
2360 assert_eq!(buffer.head_page.load(Ordering::Acquire), 1);
2361 assert_eq!(buffer.dropped_pages(), 1);
2362 assert_ne!(
2363 buffer.nodes[0].active_writers.load(Ordering::Acquire) & PAGE_ACTIVE_BIT,
2364 0,
2365 "Stale advance_to_next_page must not clear PAGE_ACTIVE_BIT on the active tail page"
2366 );
2367
2368 let (res3, _, _) = buffer.reserve(100).unwrap();
2370 assert_eq!(res3.node_idx, 0);
2371 buffer.commit(res3);
2372 }
2373
2374 #[test]
2375 fn test_new_rejects_more_nodes_than_tail_val_can_index() {
2376 let size_bytes = (TailVal::MAX_NODES + 1) * (*PAGE_SIZE) as usize;
2378 assert_eq!(
2379 LocklessRingBuffer::new(size_bytes, true, fuchsia_trace::Id::new()).err(),
2380 Some(errno!(ENOMEM))
2381 );
2382 }
2383
2384 #[test]
2385 fn test_enable_on_new_ring_returns_creation_time() {
2386 let before_new = zx::BootInstant::get();
2387 let buffer =
2388 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2389 .unwrap();
2390 let after_new = zx::BootInstant::get();
2391
2392 let created_at = buffer.enable().unwrap();
2395 assert!(before_new <= created_at && created_at <= after_new);
2396 }
2397
2398 #[test]
2401 fn test_enable_with_reservation_in_flight_is_noop() {
2402 let buffer =
2403 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2404 .unwrap();
2405
2406 buffer.disable().unwrap();
2408 let enabled_at = buffer.enable().unwrap();
2409
2410 let (res, _, _) = buffer.reserve(100).unwrap();
2412 assert_eq!(res.node_idx, 0);
2413 let ref_before = buffer.ref_count.load(Ordering::Acquire);
2414
2415 assert_eq!(buffer.enable(), Ok(enabled_at));
2417 assert_eq!(buffer.ref_count.load(Ordering::Acquire), ref_before);
2418 assert_eq!(
2419 buffer.nodes[0].write_offset.load(Ordering::Relaxed),
2420 LocklessRingBuffer::PAGE_HEADER_SIZE + 100
2421 );
2422
2423 buffer.commit(res);
2425 assert_eq!(buffer.ref_count.load(Ordering::Acquire), RING_ENABLED_BIT);
2426 assert_eq!(buffer.nodes[0].active_writers.load(Ordering::Acquire) & ACTIVE_WRITERS_MASK, 0);
2427 }
2428
2429 #[test]
2432 fn test_enable_with_read_in_flight_is_noop() {
2433 struct EnablingOutputBuffer<'a> {
2434 buffer: &'a LocklessRingBuffer,
2435 enabled_at: zx::BootInstant,
2436 }
2437 impl std::fmt::Debug for EnablingOutputBuffer<'_> {
2438 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2439 f.debug_struct("EnablingOutputBuffer").finish_non_exhaustive()
2440 }
2441 }
2442 impl OutputBuffer for EnablingOutputBuffer<'_> {
2443 fn write_each(
2444 &mut self,
2445 _callback: &mut OutputBufferCallback<'_>,
2446 ) -> Result<usize, Errno> {
2447 Ok(0)
2448 }
2449 fn available(&self) -> usize {
2450 assert_eq!(self.buffer.ref_count.load(Ordering::Acquire), RING_ENABLED_BIT | 1);
2452 assert_eq!(self.buffer.enable(), Ok(self.enabled_at));
2453 assert_eq!(self.buffer.ref_count.load(Ordering::Acquire), RING_ENABLED_BIT | 1);
2454 (*PAGE_SIZE) as usize
2455 }
2456 }
2457
2458 let buffer =
2459 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2460 .unwrap();
2461 buffer.disable().unwrap();
2462 let enabled_at = buffer.enable().unwrap();
2463
2464 let mut dest = EnablingOutputBuffer { buffer: &buffer, enabled_at };
2466 assert_eq!(buffer.read(&mut dest), error!(EAGAIN));
2467
2468 assert_eq!(buffer.ref_count.load(Ordering::Acquire), RING_ENABLED_BIT);
2470 }
2471
2472 #[test]
2473 fn test_reenable_resets_ring() {
2474 let buffer =
2475 LocklessRingBuffer::new(3 * (*PAGE_SIZE) as usize, true, fuchsia_trace::Id::new())
2476 .unwrap();
2477 let max_payload = (*PAGE_SIZE) as usize - LocklessRingBuffer::PAGE_HEADER_SIZE;
2478
2479 let (res, _, _) = buffer.reserve(100).unwrap();
2481 assert_eq!(res.node_idx, 0);
2482 buffer.commit(res);
2483 let (res, _, _) = buffer.reserve(max_payload).unwrap();
2484 assert_eq!(res.node_idx, 1);
2485 buffer.commit(res);
2486 let tail_before = buffer.tail_page.load(Ordering::Acquire);
2487 assert_eq!(tail_before.index(), 1);
2488
2489 buffer.disable().unwrap();
2493 let reenabled_at = buffer.enable().unwrap();
2494 assert_eq!(buffer.tail_page.load(Ordering::Acquire), tail_before.next(0));
2495 assert_eq!(
2496 buffer.nodes[0].write_offset.load(Ordering::Relaxed),
2497 LocklessRingBuffer::PAGE_HEADER_SIZE
2498 );
2499 assert_eq!(buffer.enable(), Ok(reenabled_at));
2500 }
2501}