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(target_os = "freebsd")]
89pub fn current_tid() -> u32 {
90 unsafe { libc::pthread_getthreadid_np() as u32 }
93}
94
95#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "freebsd")))]
96pub fn current_tid() -> u32 {
97 use std::sync::atomic::{AtomicU32, Ordering};
98 static NEXT: AtomicU32 = AtomicU32::new(1);
99 thread_local! { static TID: u32 = NEXT.fetch_add(1, Ordering::Relaxed); }
100 TID.with(|t| *t)
101}
102
103#[cfg(test)]
104mod tid_tests {
105 use super::*;
106
107 #[test]
108 fn cached_tid_matches_current_tid_and_is_stable() {
109 assert_eq!(cached_tid(), current_tid());
110 assert_eq!(cached_tid(), cached_tid());
111 let other = std::thread::spawn(|| (cached_tid(), current_tid()))
112 .join()
113 .unwrap();
114 assert_eq!(other.0, other.1);
115 assert_ne!(other.0, cached_tid(), "tids must differ across threads");
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use crate::buffer::MemoryBuffer;
123 use crate::recorder::recorder;
124 use crate::source::{FlushContext, Source};
125 use std::sync::Arc;
126 use std::sync::atomic::{AtomicUsize, Ordering};
127
128 #[cfg(target_os = "freebsd")]
129 #[test]
130 fn freebsd_tid_matches_kernel_thread_id() {
131 let expected = unsafe { libc::pthread_getthreadid_np() as u32 };
133 assert_eq!(current_tid(), expected);
134 }
135
136 #[derive(Default)]
137 struct Counts {
138 started: AtomicUsize,
139 stopped: AtomicUsize,
140 }
141
142 struct CountingSource {
143 counts: Arc<Counts>,
144 fails: bool,
145 }
146
147 impl Source for CountingSource {
148 fn flush(&mut self, _ctx: &FlushContext<'_>) {}
149
150 fn name(&self) -> &'static str {
151 "counting"
152 }
153
154 fn on_thread_start(&mut self) -> std::io::Result<()> {
155 if self.fails {
156 return Err(std::io::Error::other("no room for this thread"));
157 }
158 self.counts.started.fetch_add(1, Ordering::SeqCst);
159 Ok(())
160 }
161
162 fn on_thread_stop(&mut self) {
163 self.counts.stopped.fetch_add(1, Ordering::SeqCst);
164 }
165 }
166
167 fn counting_recorder(
168 sources: impl IntoIterator<Item = CountingSource>,
169 ) -> crate::recording::Recorder {
170 let mut builder = recorder(MemoryBuffer::new(64 * 1024).expect("writer"));
171 for source in sources {
172 builder = builder.source(source);
173 }
174 builder.build()
175 }
176
177 #[test]
178 fn guard_tracks_the_thread_until_it_drops() {
179 let counts = Arc::new(Counts::default());
180 let rec = counting_recorder([CountingSource {
181 counts: Arc::clone(&counts),
182 fails: false,
183 }]);
184
185 let guard = rec.handle().track_current_thread().expect("track");
186 assert_eq!(counts.started.load(Ordering::SeqCst), 1);
187 assert_eq!(counts.stopped.load(Ordering::SeqCst), 0);
188
189 drop(guard);
190 assert_eq!(counts.stopped.load(Ordering::SeqCst), 1);
191 }
192
193 #[test]
194 fn a_failing_source_rolls_back_the_started_ones() {
195 let counts = Arc::new(Counts::default());
196 let rec = counting_recorder([
197 CountingSource {
198 counts: Arc::clone(&counts),
199 fails: false,
200 },
201 CountingSource {
202 counts: Arc::clone(&counts),
203 fails: true,
204 },
205 ]);
206
207 let err = rec
208 .handle()
209 .track_current_thread()
210 .expect_err("second source fails");
211 assert_eq!(err.to_string(), "no room for this thread");
212 assert_eq!(counts.started.load(Ordering::SeqCst), 1);
214 assert_eq!(counts.stopped.load(Ordering::SeqCst), 1);
215 }
216
217 #[test]
218 fn disabled_handle_hands_back_an_inert_guard() {
219 let guard = Dial9Handle::disabled()
220 .track_current_thread()
221 .expect("inert guard");
222 drop(guard);
223 }
224}