1use std::{
4 fmt::{Formatter, Result},
5 marker::PhantomData,
6};
7
8use crate::handle::Dial9Handle;
9
10#[must_use = "dropping the guard stops tracking this thread"]
14pub struct ThreadTrackingGuard {
15 handle: Dial9Handle,
16 _not_send: PhantomData<*const ()>,
19}
20
21impl ThreadTrackingGuard {
22 pub(crate) fn new(handle: Dial9Handle) -> Self {
23 Self {
24 handle,
25 _not_send: PhantomData,
26 }
27 }
28}
29
30impl std::fmt::Debug for ThreadTrackingGuard {
31 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
32 f.debug_struct("ThreadTrackingGuard")
33 .finish_non_exhaustive()
34 }
35}
36
37impl Drop for ThreadTrackingGuard {
38 fn drop(&mut self) {
39 let Some(shared) = self.handle.shared() else {
40 return;
41 };
42 let stopped = shared.with_sources_mut(|sources| {
43 for source in sources.iter_mut() {
44 source.on_thread_stop();
45 }
46 });
47 if stopped.is_none() {
48 crate::rate_limited!(std::time::Duration::from_secs(60), {
49 tracing::warn!("sources lock poisoned, thread left tracked");
50 });
51 }
52 }
53}
54
55#[cfg(any(target_os = "linux", target_os = "android"))]
62pub fn current_tid() -> u32 {
63 unsafe { libc::syscall(libc::SYS_gettid) as u32 }
65}
66
67pub fn cached_tid() -> u32 {
73 use std::cell::Cell;
74 thread_local! {
75 static TID: Cell<u32> = const { Cell::new(0) };
76 }
77 TID.with(|tid| {
78 let cached = tid.get();
79 if cached != 0 {
80 return cached;
81 }
82 let fresh = current_tid();
83 tid.set(fresh);
84 fresh
85 })
86}
87
88#[cfg(not(any(target_os = "linux", target_os = "android")))]
89pub fn current_tid() -> u32 {
90 use std::sync::atomic::{AtomicU32, Ordering};
91 static NEXT: AtomicU32 = AtomicU32::new(1);
92 thread_local! { static TID: u32 = NEXT.fetch_add(1, Ordering::Relaxed); }
93 TID.with(|t| *t)
94}
95
96#[cfg(test)]
97mod tid_tests {
98 use super::*;
99
100 #[test]
101 fn cached_tid_matches_current_tid_and_is_stable() {
102 assert_eq!(cached_tid(), current_tid());
103 assert_eq!(cached_tid(), cached_tid());
104 let other = std::thread::spawn(|| (cached_tid(), current_tid()))
105 .join()
106 .unwrap();
107 assert_eq!(other.0, other.1);
108 assert_ne!(other.0, cached_tid(), "tids must differ across threads");
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use crate::buffer::MemoryBuffer;
116 use crate::recorder::recorder;
117 use crate::source::{FlushContext, Source};
118 use std::sync::Arc;
119 use std::sync::atomic::{AtomicUsize, Ordering};
120
121 #[derive(Default)]
122 struct Counts {
123 started: AtomicUsize,
124 stopped: AtomicUsize,
125 }
126
127 struct CountingSource {
128 counts: Arc<Counts>,
129 fails: bool,
130 }
131
132 impl Source for CountingSource {
133 fn flush(&mut self, _ctx: &FlushContext<'_>) {}
134
135 fn name(&self) -> &'static str {
136 "counting"
137 }
138
139 fn on_thread_start(&mut self) -> std::io::Result<()> {
140 if self.fails {
141 return Err(std::io::Error::other("no room for this thread"));
142 }
143 self.counts.started.fetch_add(1, Ordering::SeqCst);
144 Ok(())
145 }
146
147 fn on_thread_stop(&mut self) {
148 self.counts.stopped.fetch_add(1, Ordering::SeqCst);
149 }
150 }
151
152 fn counting_recorder(
153 sources: impl IntoIterator<Item = CountingSource>,
154 ) -> crate::recording::Recorder {
155 let mut builder = recorder(MemoryBuffer::new(64 * 1024).expect("writer"));
156 for source in sources {
157 builder = builder.source(source);
158 }
159 builder.build()
160 }
161
162 #[test]
163 fn guard_tracks_the_thread_until_it_drops() {
164 let counts = Arc::new(Counts::default());
165 let rec = counting_recorder([CountingSource {
166 counts: Arc::clone(&counts),
167 fails: false,
168 }]);
169
170 let guard = rec.handle().track_current_thread().expect("track");
171 assert_eq!(counts.started.load(Ordering::SeqCst), 1);
172 assert_eq!(counts.stopped.load(Ordering::SeqCst), 0);
173
174 drop(guard);
175 assert_eq!(counts.stopped.load(Ordering::SeqCst), 1);
176 }
177
178 #[test]
179 fn a_failing_source_rolls_back_the_started_ones() {
180 let counts = Arc::new(Counts::default());
181 let rec = counting_recorder([
182 CountingSource {
183 counts: Arc::clone(&counts),
184 fails: false,
185 },
186 CountingSource {
187 counts: Arc::clone(&counts),
188 fails: true,
189 },
190 ]);
191
192 let err = rec
193 .handle()
194 .track_current_thread()
195 .expect_err("second source fails");
196 assert_eq!(err.to_string(), "no room for this thread");
197 assert_eq!(counts.started.load(Ordering::SeqCst), 1);
199 assert_eq!(counts.stopped.load(Ordering::SeqCst), 1);
200 }
201
202 #[test]
203 fn disabled_handle_hands_back_an_inert_guard() {
204 let guard = Dial9Handle::disabled()
205 .track_current_thread()
206 .expect("inert guard");
207 drop(guard);
208 }
209}