From 42abb89530fdd898dd4084dd80ff994526e8caa9 Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Wed, 28 Oct 2026 19:35:36 +0200 Subject: [PATCH 1/3] Don't sample events captured while handling a crash --- CHANGELOG.md | 6 ++++++ src/sentry_core.c | 11 ++++++++++- tests/unit/test_basic.c | 42 +++++++++++++++++++++++++++++++++++++++++ tests/unit/tests.inc | 1 + 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bc64d4c0..af9ea3b29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +**Fixes**: + +- Events captured while a crash is being handled, such as from the `on_crash` hook, are no longer dropped by `sample_rate`, matching the crash itself. ([#XXXX](https://github.com/getsentry/sentry-native/pull/XXXX)) + ## 0.17.5 **Fixes**: diff --git a/src/sentry_core.c b/src/sentry_core.c index b8bd7343e..935bc4b74 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -43,6 +43,8 @@ static sentry_mutex_t g_options_lock = SENTRY__MUTEX_INIT; #endif /// see sentry_get_crashed_last_run() for the possible values static int g_last_crash = -1; +/// nesting depth of the crash handler, see sentry__enter_crash_handler() +static volatile long g_crash_handler_depth = 0; const sentry_options_t * sentry__options_getref(void) @@ -115,6 +117,8 @@ unregister_integrations(sentry_scope_t *scope, const sentry_options_t *options) void sentry__enter_crash_handler(void) { + sentry__atomic_fetch_and_add(&g_crash_handler_depth, 1); + const sentry_options_t *options = g_options; if (options) { for (size_t i = 0; i < options->num_integrations; i++) { @@ -138,6 +142,8 @@ sentry__exit_crash_handler(void) } } } + + sentry__atomic_fetch_and_add(&g_crash_handler_depth, -1); } int @@ -703,7 +709,10 @@ sentry__capture_event( sentry__mutex_unlock(&mut_options->session_lock); } - bool should_skip = !sentry__roll_dice(options->sample_rate); + // events captured while handling a crash belong to the crash, which + // is never sampled + bool should_skip = sentry__atomic_fetch(&g_crash_handler_depth) == 0 + && !sentry__roll_dice(options->sample_rate); if (should_skip) { SENTRY_INFO("throwing away event due to sample rate"); sentry__client_report_discard(SENTRY_DISCARD_REASON_SAMPLE_RATE, diff --git a/tests/unit/test_basic.c b/tests/unit/test_basic.c index 6d4405161..1a1daa31e 100644 --- a/tests/unit/test_basic.c +++ b/tests/unit/test_basic.c @@ -122,6 +122,48 @@ SENTRY_TEST(sampling_before_send) TEST_CHECK_INT_EQUAL(called_beforesend, 100); } +static void +counting_event_transport_func(sentry_envelope_t *envelope, void *data) +{ + uint64_t *called = data; + if (!sentry_value_is_null(sentry_envelope_get_event(envelope))) { + *called += 1; + } + sentry_envelope_free(envelope); +} + +SENTRY_TEST(sampling_crash_handler) +{ + uint64_t called_transport = 0; + + SENTRY_TEST_OPTIONS_NEW(options); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + sentry_transport_t *transport + = sentry_transport_new(counting_event_transport_func); + sentry_transport_set_state(transport, &called_transport); + sentry_options_set_transport(options, transport); + sentry_options_set_sample_rate(options, 0.0); + sentry_init(options); + + // events captured while handling a crash are not sampled, even nested + sentry__enter_crash_handler(); + sentry__enter_crash_handler(); + sentry_capture_event( + sentry_value_new_message_event(SENTRY_LEVEL_INFO, NULL, "foo")); + sentry__exit_crash_handler(); + sentry_capture_event( + sentry_value_new_message_event(SENTRY_LEVEL_INFO, NULL, "foo")); + sentry__exit_crash_handler(); + + // sampling applies again once the crash handler is left + sentry_capture_event( + sentry_value_new_message_event(SENTRY_LEVEL_INFO, NULL, "foo")); + + sentry_close(); + + TEST_CHECK_INT_EQUAL(called_transport, 2); +} + static sentry_value_t discarding_before_send( sentry_value_t event, sentry_hint_t *UNUSED(hint), void *data) diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 96d0430aa..ea2277a3f 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -387,6 +387,7 @@ XX(rwlock_stress) XX(rwlock_write_blocks_reader) XX(rwlock_write_blocks_writer) XX(sampling_before_send) +XX(sampling_crash_handler) XX(sampling_decision) XX(sampling_transaction) XX(scope_attachments) From 9b8826e2ec6cdd3795d17a38f3dfef9d3af25dab Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Wed, 28 Oct 2026 19:36:02 +0200 Subject: [PATCH 2/3] Add changelog PR link --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af9ea3b29..f31e544a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ **Fixes**: -- Events captured while a crash is being handled, such as from the `on_crash` hook, are no longer dropped by `sample_rate`, matching the crash itself. ([#XXXX](https://github.com/getsentry/sentry-native/pull/XXXX)) +- Events captured while a crash is being handled, such as from the `on_crash` hook, are no longer dropped by `sample_rate`, matching the crash itself. ([#2269](https://github.com/getsentry/sentry-native/pull/2269)) ## 0.17.5 From 8af9927da1e905be709e3f2ca60e8e9b78a977a5 Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Wed, 28 Oct 2026 19:44:11 +0200 Subject: [PATCH 3/3] Clarify crash handler depth comment --- src/sentry_core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sentry_core.c b/src/sentry_core.c index 935bc4b74..5dd346a16 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -43,7 +43,8 @@ static sentry_mutex_t g_options_lock = SENTRY__MUTEX_INIT; #endif /// see sentry_get_crashed_last_run() for the possible values static int g_last_crash = -1; -/// nesting depth of the crash handler, see sentry__enter_crash_handler() +/// > 0 while a crash is handled; nests, e.g. inproc enters on both the +/// crashing and the handler thread static volatile long g_crash_handler_depth = 0; const sentry_options_t *