1use std::sync::Arc;
6use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
7
8use crate::task::Kernel;
9use crate::vfs::OutputBuffer;
10use fuchsia_inspect::Property;
11use fuchsia_rcu::RcuDroppableArc;
12use paged_ring_buffer::LocklessRingBuffer;
13use starnix_logging::log_error;
14use starnix_sync::{LockDepMutex, PerfTraceEventQueueListLock};
15use starnix_types::PAGE_SIZE;
16use starnix_uapi::errors::Errno;
17use starnix_uapi::{errno, error};
18use zerocopy::native_endian::{I32, U16, U32, U64};
19use zerocopy::{Immutable, IntoBytes, Unaligned};
20use zx::BootTimeline;
21
22const DEFAULT_RING_BUFFER_SIZE_BYTES: usize = 2097152;
24
25const FTRACE_PRINT_ID: U16 = U16::new(5);
27
28const DROPPED_PAGES: &str = "dropped_pages";
30
31#[repr(C)]
32#[derive(Debug, Default, IntoBytes, Immutable, Unaligned)]
33struct PrintEventHeader {
34 common_type: U16,
35 common_flags: u8,
36 common_preempt_count: u8,
37 common_pid: I32,
38 ip: U64,
39}
40
41#[repr(C)]
42#[derive(Debug, IntoBytes, Immutable, Unaligned)]
43struct PrintEvent {
44 header: PrintEventHeader,
45}
46
47impl PrintEvent {
48 fn new(pid: i32) -> Self {
49 Self {
50 header: PrintEventHeader {
51 common_type: FTRACE_PRINT_ID,
52 common_pid: I32::new(pid),
53 ..Default::default()
55 },
56 }
57 }
58
59 fn size(&self) -> usize {
60 std::mem::size_of::<PrintEventHeader>()
61 }
62}
63
64#[repr(C)]
65#[derive(Debug, Default, IntoBytes, PartialEq, Immutable, Unaligned)]
66struct TraceEventHeader {
67 time_delta: U32,
71
72 data: U32,
75}
76
77impl TraceEventHeader {
78 fn new(size: usize) -> Self {
79 let size = (std::mem::size_of::<u32>() + size) as u32;
82 Self { time_delta: U32::new(0), data: U32::new(size) }
83 }
84
85 fn set_time_delta(&mut self, nanos: u32) {
86 let saturated_nanos = u64::from(nanos).min(LocklessRingBuffer::MAX_TIME_DELTA_NANOS) as u32;
90 self.time_delta = U32::new(saturated_nanos << LocklessRingBuffer::TYPE_OR_LENGTH_BITS);
92 }
93}
94
95#[repr(C)]
96#[derive(Debug, IntoBytes, Immutable, Unaligned)]
97pub struct TraceEvent {
98 header: TraceEventHeader, event: PrintEvent,
106}
107
108impl TraceEvent {
109 pub fn new(pid: i32, data_len: usize) -> Self {
110 let event = PrintEvent::new(pid);
111 let header = TraceEventHeader::new(event.size() + data_len + 1);
113 Self { header, event }
114 }
115
116 fn size(&self) -> usize {
117 std::mem::size_of::<u32>() + self.header.data.get() as usize
119 }
120}
121
122pub struct TraceEventQueue {
124 ring_buffer: RcuDroppableArc<LocklessRingBuffer>,
126
127 pub async_id_read: fuchsia_trace::Id,
129
130 pub async_id_write: fuchsia_trace::Id,
132
133 pub cpu_id: u32,
135}
136
137const STATIC_READ_TRACK_NAMES: [&str; 8] = [
138 "Tracefs read 0",
139 "Tracefs read 1",
140 "Tracefs read 2",
141 "Tracefs read 3",
142 "Tracefs read 4",
143 "Tracefs read 5",
144 "Tracefs read 6",
145 "Tracefs read 7",
146];
147
148const STATIC_WRITE_TRACK_NAMES: [&str; 8] = [
149 "Tracefs write 0",
150 "Tracefs write 1",
151 "Tracefs write 2",
152 "Tracefs write 3",
153 "Tracefs write 4",
154 "Tracefs write 5",
155 "Tracefs write 6",
156 "Tracefs write 7",
157];
158
159impl<'a> TraceEventQueue {
160 pub(crate) fn new(cpu_id: u32) -> Result<Self, Errno> {
161 Self::new_with_size(cpu_id, DEFAULT_RING_BUFFER_SIZE_BYTES)
162 }
163
164 pub(crate) fn new_with_size(cpu_id: u32, size_bytes: usize) -> Result<Self, Errno> {
165 let async_id_read = fuchsia_trace::Id::new();
166 let async_id_write = fuchsia_trace::Id::new();
167 let ring_buffer = Arc::new(
168 LocklessRingBuffer::new(size_bytes, true, async_id_write)
169 .map_err(|_| errno!(ENOMEM))?,
170 );
171 ring_buffer.disable()?;
172
173 Ok(Self {
174 ring_buffer: RcuDroppableArc::new(ring_buffer),
175 async_id_read,
176 async_id_write,
177 cpu_id,
178 })
179 }
180
181 pub fn read_track_name(&self) -> std::borrow::Cow<'static, str> {
182 if let Some(&name) = STATIC_READ_TRACK_NAMES.get(self.cpu_id as usize) {
183 std::borrow::Cow::Borrowed(name)
184 } else {
185 std::borrow::Cow::Owned(format!("Tracefs read {}", self.cpu_id))
186 }
187 }
188
189 pub fn write_track_name(&self) -> std::borrow::Cow<'static, str> {
190 if let Some(&name) = STATIC_WRITE_TRACK_NAMES.get(self.cpu_id as usize) {
191 std::borrow::Cow::Borrowed(name)
192 } else {
193 std::borrow::Cow::Owned(format!("Tracefs write {}", self.cpu_id))
194 }
195 }
196
197 fn enable(&self) -> Result<zx::BootInstant, Errno> {
198 self.ring_buffer.read().enable()
199 }
200
201 fn disable(&self) -> Result<u64, Errno> {
204 self.ring_buffer.read().disable()
205 }
206
207 pub fn read(&self, buf: &mut dyn OutputBuffer) -> Result<usize, Errno> {
212 self.ring_buffer.read().read(&mut RingOutputBuffer(buf))
213 }
214
215 pub fn push_event(
222 &self,
223 event: TraceEvent,
224 data: &[u8],
225 ) -> Result<zx::Duration<BootTimeline>, Errno> {
226 self.push_event_with_clock(event, data, zx::BootInstant::get)
227 }
228
229 fn push_event_with_clock(
232 &self,
233 mut event: TraceEvent,
234 data: &[u8],
235 current_boot_time: impl Fn() -> zx::BootInstant,
236 ) -> Result<zx::Duration<BootTimeline>, Errno> {
237 let size = event.size();
238 let ring_buffer = self.ring_buffer.read();
239
240 let (res, _timestamp, delta) = match ring_buffer.reserve_with_clock(size, current_boot_time)
241 {
242 Ok(res) => res,
243 Err(e) if e == errno!(EINVAL) => {
244 log_error!("Invalid reservation size: {}", size);
245 return Err(errno!(EINVAL));
246 }
247 Err(e) if e == errno!(ENOSPC) => {
248 log_error!("Ring buffer full, dropping event of size: {}", size);
249 return Err(errno!(ENOSPC));
250 }
251 Err(e) => return Err(e),
252 };
253
254 event.header.set_time_delta(res.header_time_delta());
255
256 let bytes = event.as_bytes();
257 res.write_at(0, bytes);
258 res.write_at(bytes.len(), data);
259 res.write_at(bytes.len() + data.len(), b"\n");
260
261 ring_buffer.commit(res);
262
263 Ok(delta)
264 }
265}
266
267pub struct TraceEventQueueList {
268 pub queues: Vec<Arc<TraceEventQueue>>,
269 state_mutex: LockDepMutex<(), PerfTraceEventQueueListLock>,
274 tracing_enabled: Arc<AtomicBool>,
275 configured_buffer_size_kb: AtomicUsize,
276 tracefs_node: fuchsia_inspect::Node,
277 buffer_size_kb_property: fuchsia_inspect::UintProperty,
278 total_capacity_bytes_property: fuchsia_inspect::UintProperty,
279}
280
281impl TraceEventQueueList {
282 pub fn from(kernel: &Kernel) -> Arc<Self> {
283 kernel.expando.get_or_init(|| {
284 let num_cpus = zx::system_get_num_cpus() as usize;
285 Self::new(num_cpus, kernel.inspect_node.create_child("tracefs"))
286 })
287 }
288
289 #[cfg(test)]
290 pub(crate) fn new_for_testing(
291 num_cpus: usize,
292 inspect_parent: &fuchsia_inspect::Node,
293 ) -> Arc<Self> {
294 Arc::new(Self::new(num_cpus, inspect_parent.create_child("tracefs")))
295 }
296
297 fn new(num_cpus: usize, tracefs_node: fuchsia_inspect::Node) -> Self {
298 let default_size_kb = DEFAULT_RING_BUFFER_SIZE_BYTES / 1024;
299 let buffer_size_kb_property =
300 tracefs_node.create_uint("buffer_size_kb", default_size_kb as u64);
301 let total_capacity_bytes_property = tracefs_node.create_uint(
302 "total_capacity_bytes",
303 (DEFAULT_RING_BUFFER_SIZE_BYTES * num_cpus) as u64,
304 );
305 let queues = (0..num_cpus)
306 .map(|cpu| Arc::new(TraceEventQueue::new(cpu as u32).expect("create queue")))
307 .collect();
308 Self {
309 queues,
310 state_mutex: Default::default(),
311 tracing_enabled: Arc::new(AtomicBool::new(false)),
312 configured_buffer_size_kb: AtomicUsize::new(default_size_kb),
313 tracefs_node,
314 buffer_size_kb_property,
315 total_capacity_bytes_property,
316 }
317 }
318
319 pub fn is_enabled(&self) -> bool {
320 self.tracing_enabled.load(Ordering::Acquire)
321 }
322
323 pub fn buffer_size_kb(&self) -> usize {
324 self.configured_buffer_size_kb.load(Ordering::Acquire)
325 }
326
327 pub fn set_buffer_size_kb(&self, size_kb: usize) -> Result<usize, Errno> {
336 if size_kb == 0 {
337 return error!(EINVAL);
338 }
339 let _lock = self.state_mutex.lock();
340 let enabled = self.is_enabled();
341
342 let page_size_kb = (*PAGE_SIZE as usize) / 1024;
343 let requested_pages = size_kb.div_ceil(page_size_kb);
344 let pages = std::cmp::max(3, requested_pages);
346 let actual_size_kb = pages.checked_mul(page_size_kb).ok_or_else(|| errno!(ENOMEM))?;
347 let target_bytes = actual_size_kb.checked_mul(1024).ok_or_else(|| errno!(ENOMEM))?;
348 let total_capacity_bytes =
349 target_bytes.checked_mul(self.queues.len()).ok_or_else(|| errno!(ENOMEM))?;
350
351 let mut new_buffers = Vec::with_capacity(self.queues.len());
353 for queue in &self.queues {
354 let ring_buffer = Arc::new(
355 LocklessRingBuffer::new(target_bytes, true, queue.async_id_write)
356 .map_err(|_| errno!(ENOMEM))?,
357 );
358 if !enabled {
359 ring_buffer.disable()?;
360 }
361 new_buffers.push(ring_buffer);
362 }
363
364 for (queue, new_buffer) in self.queues.iter().zip(new_buffers) {
366 queue.ring_buffer.update(new_buffer);
367 }
368
369 self.configured_buffer_size_kb.store(actual_size_kb, Ordering::Release);
370 self.buffer_size_kb_property.set(actual_size_kb as u64);
371 self.total_capacity_bytes_property.set(total_capacity_bytes as u64);
372
373 Ok(actual_size_kb)
374 }
375
376 pub fn enable(&self) -> Result<(), Errno> {
381 let _lock = self.state_mutex.lock();
382 let mut first_error = None;
383 for queue in &self.queues {
384 if let Err(e) = queue.enable() {
385 if first_error.is_none() {
386 first_error = Some(e);
387 }
388 }
389 }
390 if let Some(e) = first_error {
391 return Err(e);
392 }
393 self.tracing_enabled.store(true, Ordering::Release);
394 Ok(())
395 }
396
397 pub fn disable(&self) -> Result<(), Errno> {
398 let _lock = self.state_mutex.lock();
399 self.tracing_enabled.store(false, Ordering::Release);
401 let mut first_error = None;
402 let mut total_dropped = 0;
403 for queue in &self.queues {
404 match queue.disable() {
405 Ok(dropped) => total_dropped += dropped,
406 Err(e) => {
407 if first_error.is_none() {
408 first_error = Some(e);
409 }
410 }
411 }
412 }
413
414 self.tracefs_node.record_uint(DROPPED_PAGES, total_dropped);
415
416 if let Some(e) = first_error {
417 return Err(e);
418 }
419 Ok(())
420 }
421}
422
423struct RingOutputBuffer<'a>(&'a mut dyn OutputBuffer);
426
427impl paged_ring_buffer::OutputBuffer for RingOutputBuffer<'_> {
428 fn available(&self) -> usize {
429 self.0.available()
430 }
431 fn write_each(
432 &mut self,
433 callback: &mut paged_ring_buffer::OutputBufferCallback<'_>,
434 ) -> Result<usize, Errno> {
435 self.0.write_each(callback)
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use super::{
442 DEFAULT_RING_BUFFER_SIZE_BYTES, LocklessRingBuffer, PrintEvent, TraceEvent,
443 TraceEventQueue, TraceEventQueueList,
444 };
445 use crate::vfs::OutputBuffer;
446 use crate::vfs::buffers::VecOutputBuffer;
447 use diagnostics_assertions::assert_data_tree;
448 use std::sync::Arc;
449
450 use starnix_types::PAGE_SIZE;
451 use starnix_uapi::error;
452 use zerocopy::IntoBytes;
453
454 #[fuchsia::test]
455 fn trace_event_queue_empty_errors() {
456 let queue = TraceEventQueue::new(0).unwrap();
457
458 let mut buffer = VecOutputBuffer::new(*PAGE_SIZE as usize);
459 assert_eq!(queue.read(&mut buffer), error!(EAGAIN));
460
461 let data = b"B|1234|slice_name";
462 let event = TraceEvent::new(1234, data.len());
463 assert_eq!(queue.push_event(event, data), error!(ENOMEM));
464 }
465
466 #[fuchsia::test]
467 fn read_empty_queue() {
468 let queue = TraceEventQueue::new(0).expect("create queue");
469 let mut buffer = VecOutputBuffer::new(*PAGE_SIZE as usize);
470 assert_eq!(queue.read(&mut buffer), error!(EAGAIN));
471 }
472
473 #[fuchsia::test]
474 fn enable_disable_queue() {
475 let queue = TraceEventQueue::new(0).expect("create queue");
476 assert!(!queue.ring_buffer.read().is_enabled());
477
478 assert!(queue.enable().is_ok());
480
481 let data = b"B|1234|slice_name";
483 let event = TraceEvent::new(1234, data.len());
484 let result = queue.push_event(event, data);
485
486 assert!(result.is_ok());
487 assert_eq!(result.as_ref().unwrap().into_nanos(), 0);
488
489 assert!(queue.disable().is_ok());
491 assert!(!queue.ring_buffer.read().is_enabled());
492 }
493
494 #[fuchsia::test]
495 fn buffer_size_kb_configuration() {
496 let inspector = fuchsia_inspect::Inspector::default();
497 let list = TraceEventQueueList::new_for_testing(2, inspector.root());
498 assert_eq!(list.buffer_size_kb(), DEFAULT_RING_BUFFER_SIZE_BYTES / 1024);
499
500 let new_size_kb = list.set_buffer_size_kb(4096).expect("set buffer size");
502 assert_eq!(new_size_kb, 4096);
503 assert_eq!(list.buffer_size_kb(), 4096);
504 for queue in &list.queues {
505 assert_eq!(queue.ring_buffer.read().size_bytes(), 4096 * 1024);
506 assert!(!queue.ring_buffer.read().is_enabled());
507 }
508
509 let clamped_size_kb = list.set_buffer_size_kb(4).expect("clamp buffer size");
511 assert_eq!(clamped_size_kb, 12);
512 assert_eq!(list.buffer_size_kb(), 12);
513 for queue in &list.queues {
514 assert_eq!(queue.ring_buffer.read().size_bytes(), 12 * 1024);
515 assert!(!queue.ring_buffer.read().is_enabled());
516 }
517
518 assert_eq!(list.set_buffer_size_kb(0), error!(EINVAL));
520
521 list.enable().expect("enable tracing");
523 let enabled_size_kb = list.set_buffer_size_kb(2048).expect("set buffer size while enabled");
524 assert_eq!(enabled_size_kb, 2048);
525 assert_eq!(list.buffer_size_kb(), 2048);
526 for queue in &list.queues {
527 assert_eq!(queue.ring_buffer.read().size_bytes(), 2048 * 1024);
528 assert!(queue.ring_buffer.read().is_enabled());
529 }
530 list.disable().expect("disable tracing");
531 }
532
533 #[fuchsia::test]
534 async fn buffer_size_kb_inspect_updates_in_place() {
535 let inspector = fuchsia_inspect::Inspector::default();
536 let list = TraceEventQueueList::new_for_testing(2, inspector.root());
537 assert_data_tree!(inspector, root: {
538 tracefs: {
539 buffer_size_kb: (DEFAULT_RING_BUFFER_SIZE_BYTES / 1024) as u64,
540 total_capacity_bytes: (DEFAULT_RING_BUFFER_SIZE_BYTES * 2) as u64,
541 }
542 });
543
544 list.set_buffer_size_kb(4096).expect("set buffer size");
547 list.set_buffer_size_kb(1024).expect("set buffer size");
548 assert_data_tree!(inspector, root: {
549 tracefs: {
550 buffer_size_kb: 1024u64,
551 total_capacity_bytes: (1024 * 1024 * 2) as u64,
552 }
553 });
554 }
555
556 #[fuchsia::test]
557 fn create_trace_event() {
558 let event = TraceEvent::new(1234, b"B|1234|slice_name".len());
560 let event_size = event.size();
561 assert_eq!(event_size, 42);
562 }
563
564 #[fuchsia::test]
566 fn single_trace_event_fails_read() {
567 let queue = TraceEventQueue::new(0).expect("create queue");
568 queue.enable().expect("enable queue");
569 let data = b"B|1234|slice_name";
571 let event = TraceEvent::new(1234, data.len());
572
573 let result = queue.push_event(event, data);
575 assert!(result.is_ok());
576 assert_eq!(result.ok().expect("delta").into_nanos(), 0);
577
578 let mut buffer = VecOutputBuffer::new(*PAGE_SIZE as usize);
579 assert_eq!(queue.read(&mut buffer), error!(EAGAIN));
580 }
581
582 const TYPE_OR_LENGTH_MASK: u32 = (1 << LocklessRingBuffer::TYPE_OR_LENGTH_BITS) - 1;
583 const TYPE_OR_LENGTH_DATA: u32 = 0;
585
586 struct PageRecord {
588 time_stamp: Option<u64>,
590 time_delta: u32,
592 length: u32,
594 body: Vec<u8>,
596 }
597
598 fn page_records(page: &[u8]) -> Vec<PageRecord> {
602 let data_size = u64::from_le_bytes(page[8..16].try_into().unwrap()) as usize;
603 let end = LocklessRingBuffer::PAGE_HEADER_SIZE + data_size;
604 assert!(end <= page.len(), "data size {data_size} runs past the page");
605 let word = |at: usize| {
606 assert!(at + 4 <= end, "read at {at} runs past the page data ({end})");
607 u32::from_le_bytes(page[at..at + 4].try_into().unwrap())
608 };
609 let mut records = vec![];
610 let mut time_stamp = None;
611 let mut offset = LocklessRingBuffer::PAGE_HEADER_SIZE;
612 while offset < end {
613 let header = word(offset);
614 let time_delta = header >> LocklessRingBuffer::TYPE_OR_LENGTH_BITS;
615 match header & TYPE_OR_LENGTH_MASK {
616 LocklessRingBuffer::RINGBUF_TYPE_TIME_STAMP => {
617 assert!(time_stamp.is_none(), "two TIME_STAMP records in a row at {offset}");
618 let high = word(offset + 4);
619 time_stamp = Some(
620 (u64::from(high) << LocklessRingBuffer::TIME_DELTA_BITS)
621 | u64::from(time_delta),
622 );
623 offset += LocklessRingBuffer::TIME_STAMP_RECORD_SIZE;
624 }
625 TYPE_OR_LENGTH_DATA => {
626 let length = word(offset + 4);
627 let record_end = offset + 4 + length as usize;
628 assert!(record_end <= end, "record at {offset} runs past the page data");
629 let body = page[offset + 8..record_end].to_vec();
630 records.push(PageRecord {
631 time_stamp: time_stamp.take(),
632 time_delta,
633 length,
634 body,
635 });
636 offset = record_end;
637 }
638 other => panic!("unexpected record type {other} at offset {offset}"),
639 }
640 }
641 assert!(time_stamp.is_none(), "no data record follows the last TIME_STAMP record");
642 records
643 }
644
645 #[fuchsia::test]
646 fn page_overflow() {
647 let queue = TraceEventQueue::new(0).expect("create queue");
648 let queue_start_timestamp = queue.enable().expect("enable queue").into_nanos();
649
650 let pid = 1234;
651 let data = b"B|1234|loooooooooooooooooooooooooooooooooooooooooooooooooooooooooo\
652 ooooooooooooooooooooooooooooooooooooooooooooooooooooooooongevent";
653 let expected_event = TraceEvent::new(pid, data.len());
654 assert_eq!(expected_event.size(), 155);
655 let page_capacity = *PAGE_SIZE as usize - LocklessRingBuffer::PAGE_HEADER_SIZE;
656 let events_per_page = page_capacity / expected_event.size();
657 assert!(
659 events_per_page * expected_event.size() + LocklessRingBuffer::TIME_STAMP_RECORD_SIZE
660 <= page_capacity
661 );
662
663 const STEP_NANOS: i64 = 1000;
667 const GAP_EVENT: usize = 5;
668 let gap_nanos = LocklessRingBuffer::MAX_TIME_DELTA_NANOS as i64 + 1;
669 let times: Vec<i64> = (0..=events_per_page)
670 .scan(queue_start_timestamp, |time, i| {
671 *time += if i == GAP_EVENT { gap_nanos } else { STEP_NANOS };
672 Some(*time)
673 })
674 .collect();
675 let deltas: Vec<i64> = times
676 .iter()
677 .map(|&time| {
678 let event = TraceEvent::new(pid, data.len());
679 queue
680 .push_event_with_clock(event, data, || zx::BootInstant::from_nanos(time))
681 .expect("push event")
682 .into_nanos()
683 })
684 .collect();
685
686 let mut buffer = VecOutputBuffer::new(*PAGE_SIZE as usize);
687 assert_eq!(queue.read(&mut buffer), Ok(*PAGE_SIZE as usize));
688 assert_eq!(buffer.bytes_written() as u64, *PAGE_SIZE);
689 let page = buffer.data();
690 let page_timestamp = u64::from_le_bytes(page[0..8].try_into().unwrap());
692 assert_eq!(page_timestamp, times[0] as u64);
693 let data_size = u64::from_le_bytes(page[8..16].try_into().unwrap()) as usize;
696 assert_eq!(
697 data_size,
698 events_per_page * expected_event.size() + LocklessRingBuffer::TIME_STAMP_RECORD_SIZE
699 );
700
701 let records = page_records(page);
706 assert_eq!(records.len(), events_per_page);
707 let mut expected_body = PrintEvent::new(pid).as_bytes().to_vec();
708 expected_body.extend_from_slice(data);
709 expected_body.push(b'\n');
710 for (i, record) in records.iter().enumerate() {
711 assert_eq!(record.length as usize, expected_event.size() - 4, "event {i}");
712 assert_eq!(record.body, expected_body, "event {i}");
713 let (expected_delta, expected_time_delta, expected_time_stamp) = match i {
714 0 => (0, 0, None),
715 GAP_EVENT => (gap_nanos, 0, Some(times[i] as u64)),
716 _ => (STEP_NANOS, STEP_NANOS as u32, None),
717 };
718 assert_eq!(deltas[i], expected_delta, "event {i}");
719 assert_eq!(record.time_delta, expected_time_delta, "event {i}");
720 assert_eq!(record.time_stamp, expected_time_stamp, "event {i}");
721 }
722 assert_eq!(deltas[events_per_page], 0);
724
725 let mut buffer = VecOutputBuffer::new(*PAGE_SIZE as usize);
727 assert_eq!(queue.read(&mut buffer), error!(EAGAIN));
728 }
729
730 #[fuchsia::test]
737 fn concurrent_enable_disable_keeps_list_consistent() {
738 let list = TraceEventQueueList::new_for_testing(4, &fuchsia_inspect::Node::default());
739 for _ in 0..200 {
740 let barrier = Arc::new(std::sync::Barrier::new(2));
741 let enabler = {
742 let list = Arc::clone(&list);
743 let barrier = Arc::clone(&barrier);
744 std::thread::spawn(move || {
745 barrier.wait();
746 list.enable().unwrap();
747 })
748 };
749 barrier.wait();
750 list.disable().unwrap();
751 enabler.join().unwrap();
752
753 let enabled = list.is_enabled();
754 for queue in &list.queues {
755 assert_eq!(queue.ring_buffer.read().is_enabled(), enabled);
756 }
757 }
758 }
759}