sentry_core/
hub_impl.rs

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