Skip to content

Commit 3f0a366

Browse files
ref(core): Reduce unsafe access in hub switching
Use RefCell for the thread-local hub slot.
1 parent f801a2c commit 3f0a366

1 file changed

Lines changed: 30 additions & 10 deletions

File tree

sentry-core/src/hub_impl.rs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::cell::{Cell, UnsafeCell};
1+
use std::cell::{Cell, RefCell};
22
use std::marker::PhantomData;
33
use std::sync::{Arc, LazyLock, MutexGuard, PoisonError, RwLock};
44
use std::thread;
@@ -14,8 +14,8 @@ static PROCESS_HUB: LazyLock<(Arc<Hub>, thread::ThreadId)> = LazyLock::new(|| {
1414
});
1515

1616
thread_local! {
17-
static THREAD_HUB: (UnsafeCell<Arc<Hub>>, Cell<bool>) = (
18-
UnsafeCell::new(Arc::new(Hub::new_from_top(&PROCESS_HUB.0))),
17+
static THREAD_HUB: (RefCell<Arc<Hub>>, Cell<bool>) = (
18+
RefCell::new(Arc::new(Hub::new_from_top(&PROCESS_HUB.0))),
1919
Cell::new(PROCESS_HUB.1 == thread::current().id())
2020
);
2121
}
@@ -50,13 +50,11 @@ impl SwitchGuard {
5050
/// to the previous one.
5151
pub fn new(mut hub: Arc<Hub>) -> Self {
5252
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() };
53+
let mut thread_hub = thread_hub.borrow_mut();
5654
if std::ptr::eq(thread_hub.as_ref(), hub.as_ref()) {
5755
return None;
5856
}
59-
std::mem::swap(thread_hub, &mut hub);
57+
std::mem::swap(&mut *thread_hub, &mut hub);
6058
let was_process_hub = is_process_hub.replace(false);
6159
Some((hub, was_process_hub))
6260
});
@@ -69,8 +67,8 @@ impl SwitchGuard {
6967
fn swap(&mut self) -> Option<Arc<Hub>> {
7068
if let Some((mut hub, was_process_hub)) = self.inner.take() {
7169
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);
70+
let mut thread_hub = thread_hub.borrow_mut();
71+
std::mem::swap(&mut *thread_hub, &mut hub);
7472
if was_process_hub {
7573
is_process_hub.set(true);
7674
}
@@ -171,7 +169,11 @@ impl Hub {
171169
if is_process_hub.get() {
172170
f(&PROCESS_HUB.0)
173171
} else {
174-
f(unsafe { &*hub.get() })
172+
// Bind `hub` as `hub.borrow().clone()`.
173+
// It is essential we drop `hub.borrow()` before the callback, otherwise we will
174+
// panic if the callback calls `Hub::run`, so we need the binding.
175+
let hub = hub.borrow().clone();
176+
f(&hub)
175177
}
176178
})
177179
}
@@ -214,3 +216,21 @@ impl Hub {
214216
.with_mut(|stack| f(Arc::make_mut(&mut stack.top_mut().scope)))
215217
}
216218
}
219+
220+
#[cfg(test)]
221+
mod tests {
222+
use super::*;
223+
224+
/// Regression test for [`Hub::with`], ensuring that the `RefCell` borrow is not held during the callback.
225+
///
226+
/// If we hold the `RefCell` borrow during the callback, this would panic.
227+
#[test]
228+
fn hub_run_inside_with_scope() {
229+
let outer_hub = Arc::new(Hub::new(None, Default::default()));
230+
let inner_hub = Arc::new(Hub::new(None, Default::default()));
231+
232+
Hub::run(outer_hub, || {
233+
Hub::with(|_| Hub::run(inner_hub, || {}));
234+
});
235+
}
236+
}

0 commit comments

Comments
 (0)