Skip to main content

sentry_core/
hub_impl.rs

1use std::cell::{Cell, UnsafeCell};
2use std::marker::PhantomData;
3use std::sync::{Arc, LazyLock, MutexGuard, PoisonError, RwLock};
4use std::thread;
5
6use crate::Scope;
7use crate::{scope::Stack, Client, Hub};
8
9static PROCESS_HUB: LazyLock<(Arc<Hub>, thread::ThreadId)> = LazyLock::new(|| {
10    (
11        Arc::new(Hub::new(None, Arc::new(Default::default()))),
12        thread::current().id(),
13    )
14});
15
16thread_local! {
17    static THREAD_HUB: (UnsafeCell<Arc<Hub>>, Cell<bool>) = (
18        UnsafeCell::new(Arc::new(Hub::new_from_top(&PROCESS_HUB.0))),
19        Cell::new(PROCESS_HUB.1 == thread::current().id())
20    );
21}
22
23/// A guard that temporarily swaps the active hub in thread-local storage.
24///
25/// This type is `!Send` because it manages thread-local state and must be
26/// dropped on the same thread where it was created.
27pub struct SwitchGuard {
28    inner: Option<(Arc<Hub>, bool)>,
29    /// Makes this type `!Send` while keeping it `Sync`.
30    ///
31    /// ```rust
32    /// # use sentry_core::HubSwitchGuard as SwitchGuard;
33    /// trait AssertSync: Sync {}
34    ///
35    /// impl AssertSync for SwitchGuard {}
36    /// ```
37    ///
38    /// ```rust,compile_fail
39    /// # use sentry_core::HubSwitchGuard as SwitchGuard;
40    /// trait AssertSend: Send {}
41    ///
42    /// impl AssertSend for SwitchGuard {}
43    /// ```
44    _not_send: PhantomData<MutexGuard<'static, ()>>,
45}
46
47impl SwitchGuard {
48    /// Swaps the current thread's Hub by the one provided
49    /// and returns a guard that, when dropped, replaces it
50    /// to the previous one.
51    pub fn new(mut hub: Arc<Hub>) -> Self {
52        let inner = THREAD_HUB.with(|(thread_hub, is_process_hub)| {
53            // SAFETY: `thread_hub` will always be a valid thread local hub,
54            // by definition not shared between threads.
55            let thread_hub = unsafe { &mut *thread_hub.get() };
56            if std::ptr::eq(thread_hub.as_ref(), hub.as_ref()) {
57                return None;
58            }
59            std::mem::swap(thread_hub, &mut hub);
60            let was_process_hub = is_process_hub.replace(false);
61            Some((hub, was_process_hub))
62        });
63        SwitchGuard {
64            inner,
65            _not_send: PhantomData,
66        }
67    }
68
69    fn swap(&mut self) -> Option<Arc<Hub>> {
70        if let Some((mut hub, was_process_hub)) = self.inner.take() {
71            Some(THREAD_HUB.with(|(thread_hub, is_process_hub)| {
72                let thread_hub = unsafe { &mut *thread_hub.get() };
73                std::mem::swap(thread_hub, &mut hub);
74                if was_process_hub {
75                    is_process_hub.set(true);
76                }
77                hub
78            }))
79        } else {
80            None
81        }
82    }
83}
84
85impl Drop for SwitchGuard {
86    fn drop(&mut self) {
87        let _ = self.swap();
88    }
89}
90
91#[derive(Debug)]
92pub(crate) struct HubImpl {
93    pub(crate) stack: Arc<RwLock<Stack>>,
94}
95
96impl HubImpl {
97    pub(crate) fn with<F: FnOnce(&Stack) -> R, R>(&self, f: F) -> R {
98        let guard = self.stack.read().unwrap_or_else(PoisonError::into_inner);
99        f(&guard)
100    }
101
102    pub(crate) fn with_mut<F: FnOnce(&mut Stack) -> R, R>(&self, f: F) -> R {
103        let mut guard = self.stack.write().unwrap_or_else(PoisonError::into_inner);
104        f(&mut guard)
105    }
106
107    pub(crate) fn is_active_and_usage_safe(&self) -> bool {
108        let guard = match self.stack.read() {
109            Err(err) => err.into_inner(),
110            Ok(guard) => guard,
111        };
112
113        guard.top().client.as_ref().is_some_and(|c| c.is_enabled())
114    }
115}
116
117impl Hub {
118    /// Creates a new hub from the given client and scope.
119    pub fn new(client: Option<Arc<Client>>, scope: Arc<Scope>) -> Hub {
120        Hub {
121            inner: HubImpl {
122                stack: Arc::new(RwLock::new(Stack::from_client_and_scope(client, scope))),
123            },
124            last_event_id: RwLock::new(None),
125        }
126    }
127
128    /// Creates a new hub based on the top scope of the given hub.
129    pub fn new_from_top<H: AsRef<Hub>>(other: H) -> Hub {
130        let hub = other.as_ref();
131        hub.inner.with(|stack| {
132            let top = stack.top();
133            Hub::new(top.client.clone(), top.scope.clone())
134        })
135    }
136
137    /// Returns the current, thread-local hub.
138    ///
139    /// Invoking this will return the current thread-local hub.  The first
140    /// time it is called on a thread, a new thread-local hub will be
141    /// created based on the topmost scope of the hub on the main thread as
142    /// returned by [`Hub::main`].  If the main thread did not yet have a
143    /// hub it will be created when invoking this function.
144    ///
145    /// To have control over which hub is installed as the current
146    /// thread-local hub, use [`Hub::run`].
147    ///
148    /// This method is unavailable if the client implementation is disabled.
149    /// When using the minimal API set use [`Hub::with_active`] instead.
150    pub fn current() -> Arc<Hub> {
151        Hub::with(Arc::clone)
152    }
153
154    /// Returns the main thread's hub.
155    ///
156    /// This is similar to [`Hub::current`] but instead of picking the
157    /// current thread's hub it returns the main thread's hub instead.
158    pub fn main() -> Arc<Hub> {
159        PROCESS_HUB.0.clone()
160    }
161
162    /// Invokes the callback with the default hub.
163    ///
164    /// This is a slightly more efficient version than [`Hub::current`] and
165    /// also unavailable in minimal mode.
166    pub fn with<F, R>(f: F) -> R
167    where
168        F: FnOnce(&Arc<Hub>) -> R,
169    {
170        THREAD_HUB.with(|(hub, is_process_hub)| {
171            if is_process_hub.get() {
172                f(&PROCESS_HUB.0)
173            } else {
174                f(unsafe { &*hub.get() })
175            }
176        })
177    }
178
179    /// Binds a hub to the current thread for the duration of the call.
180    ///
181    /// During the execution of `f` the given hub will be installed as the
182    /// thread-local hub.  So any call to [`Hub::current`] during this time
183    /// will return the provided hub.
184    ///
185    /// Once the function is finished executing, including after it
186    /// panicked, the original hub is re-installed if one was present.
187    pub fn run<F: FnOnce() -> R, R>(hub: Arc<Hub>, f: F) -> R {
188        let _guard = SwitchGuard::new(hub);
189        f()
190    }
191
192    /// Returns the currently bound client.
193    pub fn client(&self) -> Option<Arc<Client>> {
194        self.inner.with(|stack| stack.top().client.clone())
195    }
196
197    /// Binds a new client to the hub.
198    pub fn bind_client(&self, client: Option<Arc<Client>>) {
199        self.inner.with_mut(|stack| {
200            stack.top_mut().client = client;
201        })
202    }
203
204    pub(crate) fn is_active_and_usage_safe(&self) -> bool {
205        self.inner.is_active_and_usage_safe()
206    }
207
208    pub(crate) fn with_current_scope<F: FnOnce(&Scope) -> R, R>(&self, f: F) -> R {
209        self.inner.with(|stack| f(&stack.top().scope))
210    }
211
212    pub(crate) fn with_current_scope_mut<F: FnOnce(&mut Scope) -> R, R>(&self, f: F) -> R {
213        self.inner
214            .with_mut(|stack| f(Arc::make_mut(&mut stack.top_mut().scope)))
215    }
216}