Skip to content

Commit 0e92db7

Browse files
authored
fix: Avoid Deadlock popping ScopeGuards out of order (#536)
The `ScopeGuard` panics when it is being dropped out of order. Previously it would do so while holding the Scope/stack `RwLock`. The `PanicIntegration` would also try to acquire that same lock in order to capture the resulting panic, resulting in a deadlock or a double-panic in case the `RwLock` implementation detected the deadlock.
1 parent 104cfae commit 0e92db7

File tree

3 files changed

+64
-4
lines changed

3 files changed

+64
-4
lines changed

sentry-core/src/performance.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,7 @@ impl Transaction {
554554
let sample_profile = inner.profiler_guard.take().and_then(|profiler_guard| {
555555
profiling::finish_profiling(&transaction, profiler_guard, inner.context.trace_id)
556556
});
557+
drop(inner);
557558

558559
let mut envelope = protocol::Envelope::new();
559560
envelope.add_item(transaction);

sentry-core/src/scope/real.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,24 @@ impl fmt::Debug for ScopeGuard {
125125
impl Drop for ScopeGuard {
126126
fn drop(&mut self) {
127127
if let Some((stack, depth)) = self.0.take() {
128-
let mut stack = stack.write().unwrap_or_else(PoisonError::into_inner);
129-
if stack.depth() != depth {
130-
panic!("Tried to pop guards out of order");
128+
let popped_depth = {
129+
let mut stack = stack.write().unwrap_or_else(PoisonError::into_inner);
130+
let popped_depth = stack.depth();
131+
stack.pop();
132+
popped_depth
133+
};
134+
// NOTE: We need to drop the `stack` lock before panicing, as the
135+
// `PanicIntegration` will want to lock the `stack` itself
136+
// (through `capture_event` -> `HubImpl::with`), and would thus
137+
// result in a deadlock.
138+
// Though that deadlock itself is detected by the `RwLock` (on macOS)
139+
// and results in its own panic: `rwlock read lock would result in deadlock`.
140+
// However that panic happens in the panic handler and will thus
141+
// ultimately result in a `thread panicked while processing panic. aborting.`
142+
// Long story short, we should not panic while holding the lock :-)
143+
if popped_depth != depth {
144+
panic!("Popped scope guard out of order");
131145
}
132-
stack.pop();
133146
}
134147
}
135148
}

sentry/tests/test_basic.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,49 @@ fn test_attachment_sent_from_scope() {
199199
&& attachment.buffer == vec![1, 2, 3, 4, 5, 6, 7, 8, 9]
200200
));
201201
}
202+
203+
#[cfg(feature = "panic")]
204+
#[test]
205+
fn test_panic_scope_pop() {
206+
let options = sentry::ClientOptions::new()
207+
.add_integration(sentry::integrations::panic::PanicIntegration::new());
208+
209+
let events = sentry::test::with_captured_events_options(
210+
|| {
211+
// in case someone wants to log the original panics:
212+
// let next = std::panic::take_hook();
213+
// std::panic::set_hook(Box::new(move |info| {
214+
// dbg!(&info);
215+
// println!("{}", std::backtrace::Backtrace::force_capture());
216+
// next(info);
217+
// }));
218+
219+
let hub = sentry::Hub::current();
220+
let scope1 = hub.push_scope();
221+
let scope2 = hub.push_scope();
222+
223+
let panic = std::panic::catch_unwind(|| {
224+
drop(scope1);
225+
});
226+
assert!(panic.is_err());
227+
228+
let panic = std::panic::catch_unwind(|| {
229+
drop(scope2);
230+
});
231+
assert!(panic.is_err());
232+
},
233+
options,
234+
);
235+
236+
assert_eq!(events.len(), 2);
237+
assert_eq!(&events[0].exception[0].ty, "panic");
238+
assert_eq!(
239+
events[0].exception[0].value,
240+
Some("Popped scope guard out of order".into())
241+
);
242+
assert_eq!(&events[1].exception[0].ty, "panic");
243+
assert_eq!(
244+
events[1].exception[0].value,
245+
Some("Popped scope guard out of order".into())
246+
);
247+
}

0 commit comments

Comments
 (0)