sentry_core/
hub_impl.rs

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