Skip to content

Commit

Permalink
fix: Avoid Deadlock popping ScopeGuards out of order (#536)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Swatinem authored Jan 13, 2023
1 parent 104cfae commit 0e92db7
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 4 deletions.
1 change: 1 addition & 0 deletions sentry-core/src/performance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@ impl Transaction {
let sample_profile = inner.profiler_guard.take().and_then(|profiler_guard| {
profiling::finish_profiling(&transaction, profiler_guard, inner.context.trace_id)
});
drop(inner);

let mut envelope = protocol::Envelope::new();
envelope.add_item(transaction);
Expand Down
21 changes: 17 additions & 4 deletions sentry-core/src/scope/real.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,24 @@ impl fmt::Debug for ScopeGuard {
impl Drop for ScopeGuard {
fn drop(&mut self) {
if let Some((stack, depth)) = self.0.take() {
let mut stack = stack.write().unwrap_or_else(PoisonError::into_inner);
if stack.depth() != depth {
panic!("Tried to pop guards out of order");
let popped_depth = {
let mut stack = stack.write().unwrap_or_else(PoisonError::into_inner);
let popped_depth = stack.depth();
stack.pop();
popped_depth
};
// NOTE: We need to drop the `stack` lock before panicing, as the
// `PanicIntegration` will want to lock the `stack` itself
// (through `capture_event` -> `HubImpl::with`), and would thus
// result in a deadlock.
// Though that deadlock itself is detected by the `RwLock` (on macOS)
// and results in its own panic: `rwlock read lock would result in deadlock`.
// However that panic happens in the panic handler and will thus
// ultimately result in a `thread panicked while processing panic. aborting.`
// Long story short, we should not panic while holding the lock :-)
if popped_depth != depth {
panic!("Popped scope guard out of order");
}
stack.pop();
}
}
}
Expand Down
46 changes: 46 additions & 0 deletions sentry/tests/test_basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,49 @@ fn test_attachment_sent_from_scope() {
&& attachment.buffer == vec![1, 2, 3, 4, 5, 6, 7, 8, 9]
));
}

#[cfg(feature = "panic")]
#[test]
fn test_panic_scope_pop() {
let options = sentry::ClientOptions::new()
.add_integration(sentry::integrations::panic::PanicIntegration::new());

let events = sentry::test::with_captured_events_options(
|| {
// in case someone wants to log the original panics:
// let next = std::panic::take_hook();
// std::panic::set_hook(Box::new(move |info| {
// dbg!(&info);
// println!("{}", std::backtrace::Backtrace::force_capture());
// next(info);
// }));

let hub = sentry::Hub::current();
let scope1 = hub.push_scope();
let scope2 = hub.push_scope();

let panic = std::panic::catch_unwind(|| {
drop(scope1);
});
assert!(panic.is_err());

let panic = std::panic::catch_unwind(|| {
drop(scope2);
});
assert!(panic.is_err());
},
options,
);

assert_eq!(events.len(), 2);
assert_eq!(&events[0].exception[0].ty, "panic");
assert_eq!(
events[0].exception[0].value,
Some("Popped scope guard out of order".into())
);
assert_eq!(&events[1].exception[0].ty, "panic");
assert_eq!(
events[1].exception[0].value,
Some("Popped scope guard out of order".into())
);
}

0 comments on commit 0e92db7

Please sign in to comment.