Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ jobs:
- name: Run cargo test
run: cargo test --workspace --all-features --all-targets --locked

# `--all-features` turns on `scope`, so also test the default mode,
# which sends the main hub's scope.
- name: Test sentry-minidump default features
run: cargo test -p sentry-minidump --locked

- name: Run cargo doc tests
run: cargo test --workspace --all-features --doc --locked

Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## Unreleased

Comment thread
szokeasaurusrex marked this conversation as resolved.
### Features

- The `sentry-minidump` crash event now carries the main hub's scope. Nothing is sent to the crash reporter until the crash: a helper thread serializes the scope before the minidump is requested. The new `scope_timeout` option bounds the wait for the scope. With the new `minidump-scope` feature, the crash event carries the scope of the thread that crashed instead, including hubs bound with `Hub::run`. This adds a few nanoseconds to each hub switch ([#1340](https://github.com/getsentry/sentry-rust/pull/1340)).
- Added `Hub::scope_for_os_thread` to `sentry-core`, which returns the scope of the hub current on the thread with a given operating system thread id. It tracks threads only with the new `thread-registry` feature, which `minidump-scope` turns on, and otherwise returns the main hub's scope ([#1340](https://github.com/getsentry/sentry-rust/pull/1340)).

### Deprecations

- Deprecated the `set_user`, `set_tag`, `set_extra` and `add_breadcrumb` methods on `MinidumpIntegration`. The crash event now carries the main hub's scope, or with `minidump-scope` the crashing thread's scope, so set these values with `sentry::configure_scope` and `sentry::add_breadcrumb` instead ([#1340](https://github.com/getsentry/sentry-rust/pull/1340)).

## 0.49.3

### Features
Expand Down
7 changes: 5 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ http = "1.0.0"
httpdate = "1.0.0"
libc = "0.2.66"
log = "0.4.8"
minidumper-child = "0.5"
mach2 = "0.4"
minidumper-child = "0.6"
native-tls = "0.2.8"
opentelemetry = { version = "0.32.0", default-features = false }
opentelemetry_sdk = { version = "0.32.1", default-features = false }
Expand Down
4 changes: 4 additions & 0 deletions sentry-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ default = []
client = ["rand"]
test = ["client", "release-health"]
release-health = []
thread-registry = ["client", "dep:libc"]
logs = []
metrics = []

Expand All @@ -39,6 +40,9 @@ serde_json = { workspace = true }
url = { workspace = true }
uuid = { workspace = true, features = ["v4", "serde"], optional = true }

[target.'cfg(unix)'.dependencies]
libc = { workspace = true, optional = true }

[dev-dependencies]
# Because we re-export all the public API in `sentry`, we actually run all the
# doctests using the `sentry` crate. This also takes care of the doctest
Expand Down
6 changes: 6 additions & 0 deletions sentry-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ functionality.
- `feature = "test"`: Activates the [`test`] module, which can be used to
write integration tests. It comes with a test transport which can capture
all sent events for inspection.
- `feature = "thread-registry"`: Tracks the hub current on each thread,
so a crash reporter can get the crashing thread's scope with
`Hub::scope_for_os_thread`. Every hub switch also updates the
registry. In the `hub-switch` benchmark this takes `Hub::run` from
about 3.5 ns to about 11 ns. The `minidump-scope` feature of `sentry`
turns this on.

[Sentry]: https://sentry.io/
[`sentry`]: https://crates.io/crates/sentry
Expand Down
27 changes: 27 additions & 0 deletions sentry-core/benches/scope_benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@
//! We have the following tests:
//! * [`scope_with_breadcrumbs`]
//! * [`scope_with_tags`]
//! * Switching hubs with [`sentry::Hub::run`], and the first use of a hub on
//! a new thread. The `thread-registry` feature adds work to both, so
//! compare runs with and without it:
//!
//! ```text
//! $ cargo bench -p sentry-core --bench scope_benchmark -- hub-switch
//! $ cargo bench -p sentry-core --bench scope_benchmark --features thread-registry -- hub-switch
//! ```
//!
//! We do test the following permutations:
//! * No active Hub is bound, meaning most API functions will just noop
Expand Down Expand Up @@ -192,6 +200,25 @@ fn scope_benchmark(c: &mut Criterion) {
}

group.finish();

#[cfg(feature = "client")]
{
let mut group = c.benchmark_group("hub-switch");

group.bench_function("run", |b| {
let hub = Arc::new(discarding_hub());
b.iter(|| sentry::Hub::run(hub.clone(), || ()))
});
group.bench_function("new-thread", |b| {
b.iter(|| {
std::thread::spawn(|| drop(sentry::Hub::current()))
.join()
.unwrap()
})
});

group.finish();
}
}

criterion_group!(benches, scope_benchmark);
Expand Down
45 changes: 40 additions & 5 deletions sentry-core/src/hub_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@ thread_local! {
/// [`PROCESS_HUB`] are identical, i.e. `Arc::ptr_eq(&PROCESS_HUB, &THREAD_HUB)` is true.
/// On any other thread, the [`THREAD_HUB`] is created as a new hub based off of the
/// [`PROCESS_HUB`].
static THREAD_HUB: RefCell<Arc<Hub>> = if thread::current().id() == PROCESS_HUB.thread {
PROCESS_HUB.hub.clone()
} else {
Hub::new_from_top(&PROCESS_HUB.hub).into()
}.into()
static THREAD_HUB: RefCell<Arc<Hub>> = {
let hub: Arc<Hub> = if thread::current().id() == PROCESS_HUB.thread {
PROCESS_HUB.hub.clone()
} else {
Hub::new_from_top(&PROCESS_HUB.hub).into()
};
#[cfg(feature = "thread-registry")]
crate::thread_registry::set_current_hub(&hub);
Comment thread
timfish marked this conversation as resolved.
hub.into()
}
}

/// A guard that temporarily swaps the active hub in thread-local storage.
Expand Down Expand Up @@ -61,6 +66,8 @@ impl SwitchGuard {
return None;
}
std::mem::swap(&mut *thread_hub, &mut hub);
#[cfg(feature = "thread-registry")]
crate::thread_registry::set_current_hub(&thread_hub);
Some(hub)
});
SwitchGuard {
Expand All @@ -80,6 +87,8 @@ impl SwitchGuard {
.try_with(|thread_hub| {
let mut thread_hub = thread_hub.borrow_mut();
std::mem::swap(&mut *thread_hub, &mut hub);
#[cfg(feature = "thread-registry")]
crate::thread_registry::set_current_hub(&thread_hub);
hub
})
.ok()
Expand Down Expand Up @@ -164,6 +173,32 @@ impl Hub {
PROCESS_HUB.hub.clone()
}

/// Returns the scope of the hub that is current on the thread with the
/// given operating system thread id.
///
/// The id is the one a crash handler reports for the crashing thread:
/// the kernel thread id on Linux and Android, `GetCurrentThreadId` on
/// Windows, and the Mach port name on macOS.
///
/// Threads are only tracked with the `thread-registry` feature, and
/// then hubs bound with [`Hub::run`] are tracked too. Without it, and
/// for a thread that never used a hub or has exited, this returns the
/// main hub's scope. That is the scope such a thread inherits when it
/// first uses [`Hub::current`].
///
/// This only takes the hub's read lock, so it does not wait for other
/// threads that read the scope.
pub fn scope_for_os_thread(os_thread_id: u64) -> Arc<Scope> {
#[cfg(feature = "thread-registry")]
let hub = crate::thread_registry::hub_for_os_thread(os_thread_id).unwrap_or_else(Hub::main);
#[cfg(not(feature = "thread-registry"))]
let hub = {
let _ = os_thread_id;
Hub::main()
};
hub.inner.with(|stack| stack.top().scope.clone())
}

/// Invokes the callback with the default hub.
#[deprecated = "Use `Hub::current` instead; this function offers no performance benefit."]
pub fn with<F, R>(f: F) -> R
Expand Down
8 changes: 8 additions & 0 deletions sentry-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@
//! - `feature = "test"`: Activates the [`test`] module, which can be used to
//! write integration tests. It comes with a test transport which can capture
//! all sent events for inspection.
//! - `feature = "thread-registry"`: Tracks the hub current on each thread,
//! so a crash reporter can get the crashing thread's scope with
//! [`Hub::scope_for_os_thread`]. Every hub switch also updates the
//! registry. In the `hub-switch` benchmark this takes [`Hub::run`] from
//! about 3.5 ns to about 11 ns. The `minidump-scope` feature of `sentry`
//! turns this on.
//!
//! [Sentry]: https://sentry.io/
//! [`sentry`]: https://crates.io/crates/sentry
Expand Down Expand Up @@ -146,6 +152,8 @@ mod client;
mod hub_impl;
#[cfg(feature = "client")]
mod session;
#[cfg(feature = "thread-registry")]
mod thread_registry;

#[cfg(feature = "client")]
pub use crate::clientoptions::MaxRequestBodySize;
Expand Down
Loading