Skip to content

Commit b5a7d8e

Browse files
authored
ref: Switch Hubs to using a Guard (#524)
Use a `SwitchGuard` internally as a replacement for `Hub::run` which does not require passing a closure, and does not go through `panic::catch_unwind`, as `Drop` impls are guaranteed to be executed even when panics are unwinding.
1 parent 29f7fc1 commit b5a7d8e

File tree

3 files changed

+62
-60
lines changed

3 files changed

+62
-60
lines changed

Diff for: sentry-core/Cargo.toml

+4-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ harness = false
2121

2222
[features]
2323
default = []
24-
client = ["rand"]
24+
client = ["rand", "arc-swap"]
2525
# I would love to just have a `log` feature, but this is used inside a macro,
2626
# and macros actually expand features (and extern crate) where they are used!
2727
debug-logs = ["dep:log"]
@@ -41,10 +41,11 @@ sys-info = { version = "0.9.1", optional = true }
4141
build_id = { version = "0.2.1", optional = true }
4242
findshlibs = { version = "=0.10.2", optional = true }
4343
rustc_version_runtime = { version = "0.2.1", optional = true }
44-
indexmap = { version = "1.9.1", optional = true}
44+
indexmap = { version = "1.9.1", optional = true }
45+
arc-swap = { version = "1.5.1", optional = true }
4546

4647
[target.'cfg(target_family = "unix")'.dependencies]
47-
pprof = { version = "0.11.0", optional = true, default-features = false}
48+
pprof = { version = "0.11.0", optional = true, default-features = false }
4849
libc = { version = "^0.2.66", optional = true }
4950

5051
[dev-dependencies]

Diff for: sentry-core/src/futures.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ where
3737
let future = unsafe { self.map_unchecked_mut(|s| &mut s.future) };
3838
#[cfg(feature = "client")]
3939
{
40-
Hub::run(hub, || future.poll(cx))
40+
let _guard = crate::hub_impl::SwitchGuard::new(hub);
41+
future.poll(cx)
4142
}
4243
#[cfg(not(feature = "client"))]
4344
{

Diff for: sentry-core/src/hub_impl.rs

+56-56
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
use std::cell::{Cell, UnsafeCell};
1+
use std::cell::Cell;
22
use std::sync::{Arc, PoisonError, RwLock};
33
use std::thread;
44

55
use crate::Scope;
66
use crate::{scope::Stack, Client, Hub};
77

8+
use arc_swap::ArcSwap;
89
use once_cell::sync::Lazy;
910

1011
static PROCESS_HUB: Lazy<(Arc<Hub>, thread::ThreadId)> = Lazy::new(|| {
@@ -15,9 +16,51 @@ static PROCESS_HUB: Lazy<(Arc<Hub>, thread::ThreadId)> = Lazy::new(|| {
1516
});
1617

1718
thread_local! {
18-
static THREAD_HUB: UnsafeCell<Arc<Hub>> = UnsafeCell::new(
19-
Arc::new(Hub::new_from_top(&PROCESS_HUB.0)));
20-
static USE_PROCESS_HUB: Cell<bool> = Cell::new(PROCESS_HUB.1 == thread::current().id());
19+
static THREAD_HUB: (ArcSwap<Hub>, Cell<bool>) = (
20+
ArcSwap::from_pointee(Hub::new_from_top(&PROCESS_HUB.0)),
21+
Cell::new(PROCESS_HUB.1 == thread::current().id())
22+
);
23+
}
24+
25+
pub(crate) struct SwitchGuard {
26+
inner: Option<(Arc<Hub>, bool)>,
27+
}
28+
29+
impl SwitchGuard {
30+
pub(crate) fn new(hub: Arc<Hub>) -> Self {
31+
let inner = THREAD_HUB.with(|(old_hub, is_process_hub)| {
32+
{
33+
let old_hub = old_hub.load();
34+
if std::ptr::eq(old_hub.as_ref(), hub.as_ref()) {
35+
return None;
36+
}
37+
}
38+
let old_hub = old_hub.swap(hub);
39+
let was_process_hub = is_process_hub.replace(false);
40+
Some((old_hub, was_process_hub))
41+
});
42+
SwitchGuard { inner }
43+
}
44+
45+
fn swap(&mut self) -> Option<Arc<Hub>> {
46+
if let Some((old_hub, was_process_hub)) = self.inner.take() {
47+
Some(THREAD_HUB.with(|(hub, is_process_hub)| {
48+
let hub = hub.swap(old_hub);
49+
if was_process_hub {
50+
is_process_hub.set(true);
51+
}
52+
hub
53+
}))
54+
} else {
55+
None
56+
}
57+
}
58+
}
59+
60+
impl Drop for SwitchGuard {
61+
fn drop(&mut self) {
62+
let _ = self.swap();
63+
}
2164
}
2265

2366
#[derive(Debug)]
@@ -103,17 +146,13 @@ impl Hub {
103146
where
104147
F: FnOnce(&Arc<Hub>) -> R,
105148
{
106-
if USE_PROCESS_HUB.with(Cell::get) {
107-
f(&PROCESS_HUB.0)
108-
} else {
109-
// note on safety: this is safe because even though we change the Arc
110-
// by temporary binding we guarantee that the original Arc stays alive.
111-
// For more information see: run
112-
THREAD_HUB.with(|stack| unsafe {
113-
let ptr = stack.get();
114-
f(&*ptr)
115-
})
116-
}
149+
THREAD_HUB.with(|(hub, is_process_hub)| {
150+
if is_process_hub.get() {
151+
f(&PROCESS_HUB.0)
152+
} else {
153+
f(&hub.load())
154+
}
155+
})
117156
}
118157

119158
/// Binds a hub to the current thread for the duration of the call.
@@ -125,47 +164,8 @@ impl Hub {
125164
/// Once the function is finished executing, including after it
126165
/// paniced, the original hub is re-installed if one was present.
127166
pub fn run<F: FnOnce() -> R, R>(hub: Arc<Hub>, f: F) -> R {
128-
let mut restore_process_hub = false;
129-
let did_switch = THREAD_HUB.with(|ctx| unsafe {
130-
let ptr = ctx.get();
131-
if std::ptr::eq(&**ptr, &*hub) {
132-
None
133-
} else {
134-
USE_PROCESS_HUB.with(|x| {
135-
if x.get() {
136-
restore_process_hub = true;
137-
x.set(false);
138-
}
139-
});
140-
let old = (*ptr).clone();
141-
*ptr = hub.clone();
142-
Some(old)
143-
}
144-
});
145-
146-
match did_switch {
147-
None => {
148-
// None means no switch happened. We can invoke the function
149-
// just like that, no changes necessary.
150-
f()
151-
}
152-
Some(old_hub) => {
153-
use std::panic;
154-
155-
// this is for the case where we just switched the hub. This
156-
// means we need to catch the panic, restore the
157-
// old context and resume the panic if needed.
158-
let rv = panic::catch_unwind(panic::AssertUnwindSafe(f));
159-
THREAD_HUB.with(|ctx| unsafe { *ctx.get() = old_hub });
160-
if restore_process_hub {
161-
USE_PROCESS_HUB.with(|x| x.set(true));
162-
}
163-
match rv {
164-
Err(err) => panic::resume_unwind(err),
165-
Ok(rv) => rv,
166-
}
167-
}
168-
}
167+
let _guard = SwitchGuard::new(hub);
168+
f()
169169
}
170170

171171
/// Returns the currently bound client.

0 commit comments

Comments
 (0)