diff --git a/CHANGELOG.md b/CHANGELOG.md index ada5ba4f4..e8b18c9cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### New Features + +- Added `sentry::read_scope` and `Hub::read_scope`, read-only counterparts to [`configure_scope`](https://docs.rs/sentry-core/0.48.4/sentry_core/fn.configure_scope.html) that avoid cloning the scope and writing it back. The `sentry-tracing` integration now uses this to resolve the parent span when creating a new span. + ## 0.48.4 ### New Features diff --git a/sentry-core/src/api.rs b/sentry-core/src/api.rs index a4279fe35..cb403232c 100644 --- a/sentry-core/src/api.rs +++ b/sentry-core/src/api.rs @@ -157,6 +157,28 @@ where Hub::with_active(|hub| hub.configure_scope(f)) } +/// Invokes a function with read-only access to the current scope. +/// +/// The function is passed a shared reference to the [`Scope`]. Because there +/// might currently not be a scope or client active it's possible that the +/// callback might not be called at all. As a result of this the return value +/// of this closure must have a default that is returned in such cases. +/// +/// # Examples +/// +/// ``` +/// # sentry::test::with_captured_events(|| { +/// let span = sentry::read_scope(|scope| scope.get_span()); +/// # }); +/// ``` +pub fn read_scope(f: F) -> R +where + R: Default, + F: FnOnce(&Scope) -> R, +{ + Hub::with_active(|hub| hub.read_scope(f)) +} + /// Temporarily pushes a scope for a single call optionally reconfiguring it. /// /// This function takes two arguments: the first is a callback that is passed diff --git a/sentry-core/src/hub.rs b/sentry-core/src/hub.rs index b8411173a..cc9df5197 100644 --- a/sentry-core/src/hub.rs +++ b/sentry-core/src/hub.rs @@ -216,6 +216,24 @@ impl Hub { } } + /// Invokes a function with read-only access to the current scope. + /// + /// See the global [`read_scope`](crate::read_scope) + /// for more documentation. + pub fn read_scope(&self, f: F) -> R + where + R: Default, + F: FnOnce(&Scope) -> R, + { + use_without_client!(f); + with_client_impl! {{ + // Clone the `Arc` out so the callback runs without + // holding the stack lock, keeping reentrant hub calls safe. + let scope = self.inner.with(|stack| stack.top().scope.clone()); + f(&scope) + }} + } + /// Invokes a function that can modify the current scope. /// /// See the global [`configure_scope`](fn.configure_scope.html) diff --git a/sentry-tracing/src/layer/mod.rs b/sentry-tracing/src/layer/mod.rs index fba1a4e0a..23d02ffd0 100644 --- a/sentry-tracing/src/layer/mod.rs +++ b/sentry-tracing/src/layer/mod.rs @@ -309,7 +309,7 @@ where sentry_op.unwrap_or_else(|| format!("{}::{}", span.metadata().target(), span.name())); let hub = sentry_core::Hub::current(); - let parent_sentry_span = hub.configure_scope(|scope| scope.get_span()); + let parent_sentry_span = hub.read_scope(|scope| scope.get_span()); let mut sentry_span: sentry_core::TransactionOrSpan = match &parent_sentry_span { Some(parent) => parent.start_child(&sentry_op, sentry_name).into(), diff --git a/sentry/examples/performance-demo.rs b/sentry/examples/performance-demo.rs index ac7e45b5f..5c33828e2 100644 --- a/sentry/examples/performance-demo.rs +++ b/sentry/examples/performance-demo.rs @@ -37,7 +37,7 @@ fn main_span1() { let transaction_ctx = sentry::TransactionContext::continue_from_span( "background transaction", "root span", - sentry::configure_scope(|scope| scope.get_span()), + sentry::read_scope(|scope| scope.get_span()), ); thread::spawn(move || { let transaction = sentry::start_transaction(transaction_ctx); @@ -76,7 +76,7 @@ fn wrap_in_span(op: &str, description: &str, f: F) -> R where F: FnOnce() -> R, { - let parent = sentry::configure_scope(|scope| scope.get_span()); + let parent = sentry::read_scope(|scope| scope.get_span()); let span1: sentry::TransactionOrSpan = match &parent { Some(parent) => parent.start_child(op, description).into(), None => { diff --git a/sentry/tests/test_basic.rs b/sentry/tests/test_basic.rs index eb1222d5e..575779602 100644 --- a/sentry/tests/test_basic.rs +++ b/sentry/tests/test_basic.rs @@ -35,9 +35,7 @@ fn test_event_trace_context_from_propagation_context() { let mut last_event_id = None::; let mut span = None; let events = sentry::test::with_captured_events(|| { - sentry::configure_scope(|scope| { - span = scope.get_span(); - }); + span = sentry::read_scope(|scope| scope.get_span()); sentry::capture_message("Hello World!", sentry::Level::Warning); last_event_id = sentry::last_event_id(); }); @@ -160,6 +158,22 @@ fn test_reentrant_configure_scope() { assert_eq!(events[0].tags["which_scope"], "scope1"); } +#[test] +fn test_reentrant_read_scope() { + let events = sentry::test::with_captured_events(|| { + sentry::read_scope(|_scope| { + sentry::configure_scope(|scope| { + scope.set_tag("which_scope", "reentrant"); + }); + }); + + sentry::capture_message("look ma, no deadlock!", sentry::Level::Info); + }); + + assert_eq!(events.len(), 1); + assert_eq!(events[0].tags["which_scope"], "reentrant"); +} + #[test] fn test_attached_stacktrace() { let logger = sentry_log::SentryLogger::new();