tracy_client/state.rs
1use crate::Client;
2
3/// Client initialization and lifetime management.
4impl Client {
5 /// Start the client.
6 ///
7 /// The client must be started with this function before any instrumentation is invoked
8 /// anywhere in the process. This function can be called multiple times to obtain multiple
9 /// `Client` values.
10 ///
11 /// The underlying client implementation will be started up only if it wasn't already running
12 /// yet.
13 ///
14 /// Note that when the `manual-lifetime` feature is used, it is a responsibility of the user
15 /// to stop `tracy` using the [`sys::___tracy_shutdown_profiler`] function. Keep in mind that
16 /// at the time this function is called there can be no other invocations to the tracy
17 /// profiler, even from other threads (or you may get a crash!)
18 ///
19 /// # Example
20 ///
21 /// ```rust
22 /// // fn main() {
23 /// let _client = tracy_client::Client::start();
24 /// // ...
25 /// // }
26 /// ```
27 pub fn start() -> Self {
28 #[cfg(not(feature = "enable"))]
29 return Self(());
30 #[cfg(all(feature = "enable", feature = "manual-lifetime"))]
31 return manual_lifetime::start();
32 #[cfg(all(feature = "enable", not(feature = "manual-lifetime")))]
33 return Self(());
34 }
35
36 /// Obtain a client handle, but only if the client is already running.
37 #[must_use]
38 pub fn running() -> Option<Self> {
39 if Self::is_running() {
40 Some(Self(()))
41 } else {
42 None
43 }
44 }
45
46 /// Is the client already running?
47 pub fn is_running() -> bool {
48 #[cfg(not(feature = "enable"))]
49 return true; // If the client is disabled, produce a "no-op" one so that users don’t need
50 // to wory about conditional use of the instrumentation in their own code.
51 #[cfg(all(feature = "enable", feature = "manual-lifetime"))]
52 return manual_lifetime::is_running();
53 #[cfg(all(feature = "enable", not(feature = "manual-lifetime")))]
54 return true; // The client is started in life-before-main (or upon first use in case of
55 // `delayed-init`
56 }
57}
58
59impl Clone for Client {
60 /// A cheaper alternative to [`Client::start`] or [`Client::running`] when there is already a
61 /// handle handy.
62 fn clone(&self) -> Self {
63 // We already know that the state is `ENABLED`, no need to check.
64 Self(())
65 }
66}
67
68#[cfg(all(feature = "enable", feature = "manual-lifetime"))]
69mod manual_lifetime {
70 use std::sync::atomic::Ordering;
71 /// Enabling `Tracy` when it is already enabled, or Disabling when it is already disabled will
72 /// cause applications to crash. I personally think it would be better if this was a sort-of
73 /// reference counted kind-of thing so you could enable as many times as you wish and disable
74 /// just as many times without any reprecursions. At the very least this could significantly
75 /// help tests.
76 ///
77 /// We can also try to implement something like this ourselves. To do this we'd want to track 4
78 /// states that construct a following finite state machine:
79 ///
80 /// ```text
81 /// 0 = disabled -> 1 = enabling
82 /// ^ v
83 /// 3 = disabling <- 2 = enabled
84 /// ```
85 ///
86 /// And also include a reference count somewhere in there. Something we can throw in a static
87 /// would be ideal.
88 ///
89 /// Alas, Tracy's extensive use of thread-local storage presents us with another problem – we must
90 /// start up and shut down the client within the same thread. A most straightforward soution for
91 /// that would be to run a separate thread that would be dedicated entirely to just starting up and
92 /// shutting down the profiler.
93 ///
94 /// All that seems like a major pain to implement, and so we’ll punt on disabling entirely until
95 /// somebody comes with a good use-case warranting that sort of complexity.
96 #[cfg(not(loom))]
97 static CLIENT_STATE: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
98 #[cfg(loom)]
99 loom::lazy_static! {
100 static ref CLIENT_STATE: loom::sync::atomic::AtomicUsize =
101 loom::sync::atomic::AtomicUsize::new(0);
102 }
103
104 const STATE_STEP: usize = 1; // Move forward by 1 step in the FSM
105 const STATE_DISABLED: usize = 0;
106 const STATE_ENABLING: usize = STATE_DISABLED + STATE_STEP;
107 const STATE_ENABLED: usize = STATE_ENABLING + STATE_STEP;
108
109 #[inline(always)]
110 fn spin_loop() {
111 #[cfg(loom)]
112 loom::thread::yield_now();
113 #[cfg(not(loom))]
114 std::hint::spin_loop();
115 }
116
117 pub(super) fn start() -> super::Client {
118 let mut old_state = CLIENT_STATE.load(Ordering::Relaxed);
119 loop {
120 match old_state {
121 STATE_ENABLED => return super::Client(()),
122 STATE_ENABLING => {
123 while !is_running() {
124 spin_loop();
125 }
126 return super::Client(());
127 }
128 STATE_DISABLED => {
129 let result = CLIENT_STATE.compare_exchange_weak(
130 old_state,
131 STATE_ENABLING,
132 Ordering::Relaxed,
133 Ordering::Relaxed,
134 );
135 if let Err(next_old_state) = result {
136 old_state = next_old_state;
137 continue;
138 } else {
139 unsafe {
140 // SAFE: This function must not be called if the profiler has
141 // already been enabled. While in practice calling this function
142 // multiple times will only serve to trigger an assertion, we
143 // cannot exactly rely on this, since it is an undocumented
144 // behaviour and the upstream might very well just decide to invoke
145 // UB instead. In the case there are multiple copies of
146 // `tracy-client` this invariant is not actually maintained, but
147 // otherwise this is sound due to the `ENABLE_STATE` that we
148 // manage.
149 //
150 // TODO: we _could_ define `ENABLE_STATE` in the `-sys` crate...
151 let () = sys::___tracy_startup_profiler();
152 CLIENT_STATE.store(STATE_ENABLED, Ordering::Release);
153 return super::Client(());
154 }
155 }
156 }
157 _ => unreachable!(),
158 }
159 }
160 }
161
162 pub(super) fn is_running() -> bool {
163 return CLIENT_STATE.load(Ordering::Relaxed) == STATE_ENABLED;
164 }
165
166 #[cfg(test)]
167 mod test {
168 use super::*;
169
170 #[test]
171 fn state_transitions() {
172 assert_eq!(0, STATE_DISABLED);
173 assert_eq!(STATE_DISABLED.wrapping_add(STATE_STEP), STATE_ENABLING);
174 assert_eq!(STATE_ENABLING.wrapping_add(STATE_STEP), STATE_ENABLED);
175 }
176 }
177}