From 6ccb049aeff6d7e611117d1ea532b2970d0dd0ff Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 22 Jul 2026 13:32:17 +0200 Subject: [PATCH 01/22] refactor(scope): hide direct data access --- src/backends/sentry_backend_crashpad.cpp | 3 +- src/sentry_core.c | 173 ++--------- src/sentry_envelope.c | 7 +- src/sentry_scope.c | 364 +++++++++++++++++++++++ src/sentry_scope.h | 67 +++++ src/sentry_session.c | 8 +- src/sentry_tracing.c | 38 +-- tests/unit/test_logs.c | 4 +- tests/unit/test_scope.c | 146 +++++---- tests/unit/test_tracing.c | 19 +- 10 files changed, 576 insertions(+), 253 deletions(-) diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index bbb690a346..1f99e1427c 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -515,7 +515,8 @@ crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context) // written above and stays breadcrumb-free SENTRY_WITH_SCOPE (scope) { sentry_value_set_by_key(crash_event, "breadcrumbs", - sentry__ringbuffer_to_list(scope->breadcrumbs)); + sentry__ringbuffer_to_list( + sentry__scope_get_breadcrumbs(scope))); } sentry__session_replay_flush_pending( diff --git a/src/sentry_core.c b/src/sentry_core.c index 770d85dca6..5ce4310d6d 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -78,23 +78,6 @@ sentry__should_skip_upload(void) return skip; } -static void -generate_propagation_context(sentry_value_t propagation_context) -{ - sentry_value_set_by_key( - propagation_context, "trace", sentry_value_new_object()); - sentry_uuid_t trace_id = sentry_uuid_new_v4(); - sentry_uuid_t span_id = sentry_uuid_new_v4(); - sentry_value_set_by_key( - sentry_value_get_by_key(propagation_context, "trace"), "trace_id", - sentry__value_new_internal_uuid(&trace_id)); - sentry_value_set_by_key( - sentry_value_get_by_key(propagation_context, "trace"), "span_id", - sentry__value_new_span_uuid(&span_id)); - sentry__generate_sample_rand( - sentry_value_get_by_key(propagation_context, "trace")); -} - static void register_integrations(sentry_scope_t *scope, const sentry_options_t *options) { @@ -245,38 +228,7 @@ sentry_init(sentry_options_t *options) // `client_sdk` in the `scope` because some downstream SDKs want to override // it at runtime via the options interface. SENTRY_WITH_SCOPE_MUT (scope) { - if (options->sdk_name) { - sentry_value_t sdk_name - = sentry_value_new_string(options->sdk_name); - sentry_value_set_by_key(scope->client_sdk, "name", sdk_name); - } - sentry_value_t integrations - = sentry_value_get_by_key(scope->client_sdk, "integrations"); - for (size_t i = 0; i < options->num_integrations; i++) { - const char *name = options->integrations[i]->name; - if (!name) { - continue; - } - if (sentry_value_is_null(integrations)) { - integrations = sentry_value_new_list(); - sentry_value_set_by_key( - scope->client_sdk, "integrations", integrations); - } - sentry_value_append(integrations, sentry_value_new_string(name)); - } - sentry_value_freeze(scope->client_sdk); - generate_propagation_context(scope->propagation_context); - scope->release = sentry__string_clone(options->release); - scope->environment = sentry__string_clone(options->environment); - sentry_value_decref(scope->attachments); - scope->attachments = options->attachments; - options->attachments = sentry_value_new_null(); - - sentry__ringbuffer_set_max_size( - scope->breadcrumbs, options->max_breadcrumbs); - - sentry__scope_update_dsc(scope, options); - + sentry__scope_apply_options(scope, options); register_integrations(scope, options); } if (backend && backend->user_consent_changed_func) { @@ -758,9 +710,10 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, if (local_scope && sentry_value_get_length(local_scope->attachments) > 0) { // all attachments merged from multiple scopes + sentry__attachments_extend(&all_attachments, + sentry__scope_get_attachments(local_scope)); sentry__attachments_extend( - &all_attachments, local_scope->attachments); - sentry__attachments_extend(&all_attachments, scope->attachments); + &all_attachments, sentry__scope_get_attachments(scope)); attachments = all_attachments; } // otherwise only global scope has attachments @@ -986,11 +939,7 @@ void sentry_set_release_n(const char *release, size_t release_len) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_free(scope->release); - scope->release = sentry__string_clone_n(release, release_len); - sentry_value_set_by_key(scope->dynamic_sampling_context, "release", - sentry_value_new_string(scope->release)); - SENTRY_SCOPE_NOTIFY(scope, set_release, scope->release); + sentry__scope_set_release_n(scope, release, release_len); } } @@ -1004,12 +953,7 @@ void sentry_set_environment_n(const char *environment, size_t environment_len) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_free(scope->environment); - scope->environment - = sentry__string_clone_n(environment, environment_len); - sentry_value_set_by_key(scope->dynamic_sampling_context, "environment", - sentry_value_new_string(scope->environment)); - SENTRY_SCOPE_NOTIFY(scope, set_environment, scope->environment); + sentry__scope_set_environment_n(scope, environment, environment_len); } } @@ -1089,9 +1033,7 @@ void sentry_remove_tag(const char *key) { SENTRY_WITH_SCOPE_MUT (scope) { - if (sentry_value_remove_by_key(scope->tags, key) == 0) { - SENTRY_SCOPE_NOTIFY(scope, remove_tag, key); - } + sentry__scope_remove_tag(scope, key); } } @@ -1099,12 +1041,7 @@ void sentry_remove_tag_n(const char *key, size_t key_len) { SENTRY_WITH_SCOPE_MUT (scope) { - char *k - = sentry__value_remove_and_take_key_n(scope->tags, key, key_len); - if (k) { - SENTRY_SCOPE_NOTIFY(scope, remove_tag, k); - } - sentry_free(k); + sentry__scope_remove_tag_n(scope, key, key_len); } } @@ -1128,9 +1065,7 @@ void sentry_remove_extra(const char *key) { SENTRY_WITH_SCOPE_MUT (scope) { - if (sentry_value_remove_by_key(scope->extra, key) == 0) { - SENTRY_SCOPE_NOTIFY(scope, remove_extra, key); - } + sentry__scope_remove_extra(scope, key); } } @@ -1138,12 +1073,7 @@ void sentry_remove_extra_n(const char *key, size_t key_len) { SENTRY_WITH_SCOPE_MUT (scope) { - char *k - = sentry__value_remove_and_take_key_n(scope->extra, key, key_len); - if (k) { - SENTRY_SCOPE_NOTIFY(scope, remove_extra, k); - } - sentry_free(k); + sentry__scope_remove_extra_n(scope, key, key_len); } } @@ -1216,7 +1146,7 @@ void sentry__set_propagation_context(const char *key, sentry_value_t value) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_set_by_key(scope->propagation_context, key, value); + sentry__scope_set_propagation_context(scope, key, value); } } @@ -1244,9 +1174,7 @@ void sentry_remove_context(const char *key) { SENTRY_WITH_SCOPE_MUT (scope) { - if (sentry_value_remove_by_key(scope->contexts, key) == 0) { - SENTRY_SCOPE_NOTIFY(scope, remove_context, key); - } + sentry__scope_remove_context(scope, key); } } @@ -1254,12 +1182,7 @@ void sentry_remove_context_n(const char *key, size_t key_len) { SENTRY_WITH_SCOPE_MUT (scope) { - char *k = sentry__value_remove_and_take_key_n( - scope->contexts, key, key_len); - if (k) { - SENTRY_SCOPE_NOTIFY(scope, remove_context, k); - } - sentry_free(k); + sentry__scope_remove_context_n(scope, key, key_len); } } @@ -1325,7 +1248,7 @@ sentry_set_trace_n(const char *trace_id, size_t trace_id_len, sentry_uuid_t span_id = sentry_uuid_new_v4(); sentry_value_set_by_key( context, "span_id", sentry__value_new_span_uuid(&span_id)); - scope->trace_managed = false; + sentry__scope_set_trace_managed(scope, false); } if (!sentry_value_is_null(context)) { @@ -1346,8 +1269,8 @@ sentry_regenerate_trace(void) { SENTRY_WITH_OPTIONS (options) { SENTRY_WITH_SCOPE_MUT (scope) { - generate_propagation_context(scope->propagation_context); - scope->trace_managed = false; + sentry__scope_regenerate_propagation_context(scope); + sentry__scope_set_trace_managed(scope, false); sentry__scope_update_dsc(scope, options); } } @@ -1363,15 +1286,7 @@ void sentry_set_transaction_n(const char *transaction, size_t transaction_len) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_free(scope->transaction); - scope->transaction - = sentry__string_clone_n(transaction, transaction_len); - - if (scope->transaction_object) { - sentry_transaction_set_name_n( - scope->transaction_object, transaction, transaction_len); - } - SENTRY_SCOPE_NOTIFY(scope, set_transaction, scope->transaction); + sentry__scope_set_transaction_n(scope, transaction, transaction_len); } } @@ -1434,11 +1349,9 @@ sentry_transaction_start_ts(sentry_transaction_context_t *opaque_tx_ctx, // Regenerate the scope's propagation context so events // captured outside this transaction also carry the new // trace_id, and align the tx's trace_id with it. - generate_propagation_context(scope->propagation_context); + sentry__scope_regenerate_propagation_context(scope); sentry_value_t scope_trace_id = sentry_value_get_by_key( - sentry_value_get_by_key( - scope->propagation_context, "trace"), - "trace_id"); + sentry__scope_get_trace_context(scope), "trace_id"); sentry_value_incref(scope_trace_id); sentry_value_set_by_key(tx, "trace_id", scope_trace_id); sentry_value_remove_by_key(tx, "parent_span_id"); @@ -1453,8 +1366,7 @@ sentry_transaction_start_ts(sentry_transaction_context_t *opaque_tx_ctx, double sample_rand = 1.0; SENTRY_WITH_SCOPE (scope) { sample_rand = sentry_value_as_double(sentry_value_get_by_key( - sentry_value_get_by_key(scope->propagation_context, "trace"), - "sample_rand")); + sentry__scope_get_trace_context(scope), "sample_rand")); } sentry_sampling_context_t sampling_ctx = { opaque_tx_ctx, custom_sampling_ctx, NULL, sample_rand }; @@ -1500,10 +1412,7 @@ sentry_transaction_discard(sentry_transaction_t *opaque_tx) } SENTRY_WITH_SCOPE_MUT (scope) { - if (scope->transaction_object == opaque_tx) { - sentry__transaction_decref(scope->transaction_object); - scope->transaction_object = NULL; - } + sentry__scope_remove_transaction_object(scope, opaque_tx); } sentry__transaction_decref(opaque_tx); @@ -1521,27 +1430,15 @@ sentry__transaction_finish_value( sentry_value_t tx = sentry__value_clone(opaque_tx->inner); SENTRY_WITH_SCOPE_MUT (scope) { - if (scope->transaction_object) { - sentry_value_t scope_tx = scope->transaction_object->inner; - - const char *tx_id = sentry_value_as_string( - sentry_value_get_by_key(tx, "span_id")); - const char *scope_tx_id = sentry_value_as_string( - sentry_value_get_by_key(scope_tx, "span_id")); - if (sentry__string_eq(tx_id, scope_tx_id)) { - sentry__transaction_decref(scope->transaction_object); - scope->transaction_object = NULL; - } - } + sentry__scope_remove_transaction_value(scope, tx); // if the SDK manages the trace (rather than the user or a downstream // SDK) we break propagation context traces at transaction boundaries. - if (scope->trace_managed) { + if (sentry__scope_is_trace_managed(scope)) { sentry_value_t txn_trace_id = sentry_value_get_by_key(tx, "trace_id"); sentry_value_incref(txn_trace_id); - sentry_value_set_by_key( - sentry_value_get_by_key(scope->propagation_context, "trace"), + sentry_value_set_by_key(sentry__scope_get_trace_context(scope), "trace_id", txn_trace_id); } } @@ -1602,7 +1499,7 @@ void sentry_set_transaction_object(sentry_transaction_t *tx) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_scope_set_transaction_object(scope, tx); + sentry__scope_set_transaction_object(scope, tx); } } @@ -1610,7 +1507,7 @@ void sentry_set_span(sentry_span_t *span) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_scope_set_span(scope, span); + sentry__scope_set_span(scope, span); } } @@ -1762,18 +1659,7 @@ sentry_span_finish_ts(sentry_span_t *opaque_span, uint64_t timestamp) sentry_value_t span = sentry__value_clone(opaque_span->inner); SENTRY_WITH_SCOPE_MUT (scope) { - if (scope->span) { - sentry_value_t scope_span = scope->span->inner; - - const char *span_id = sentry_value_as_string( - sentry_value_get_by_key(span, "span_id")); - const char *scope_span_id = sentry_value_as_string( - sentry_value_get_by_key(scope_span, "span_id")); - if (sentry__string_eq(span_id, scope_span_id)) { - sentry__span_decref(scope->span); - scope->span = NULL; - } - } + sentry__scope_remove_span_value(scope, span); } // Note that the current API makes it impossible to set a sampled value @@ -1832,10 +1718,7 @@ sentry_span_discard(sentry_span_t *opaque_span) sentry__transaction_remove_child(opaque_span->transaction, opaque_span); SENTRY_WITH_SCOPE_MUT (scope) { - if (scope->span == opaque_span) { - sentry__span_decref(scope->span); - scope->span = NULL; - } + sentry__scope_remove_span(scope, opaque_span); } sentry__span_decref(opaque_span); diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index f94cba4639..672e38deeb 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -415,10 +415,9 @@ sentry__envelope_add_event(sentry_envelope_t *envelope, sentry_value_t event) sentry_value_t dsc = sentry_value_new_null(); double sample_rand = (double)NAN; SENTRY_WITH_SCOPE (scope) { - dsc = sentry__value_clone(scope->dynamic_sampling_context); + dsc = sentry__value_clone(sentry__scope_get_dsc(scope)); sample_rand = sentry_value_as_double(sentry_value_get_by_key( - sentry_value_get_by_key(scope->propagation_context, "trace"), - "sample_rand")); + sentry__scope_get_trace_context(scope), "sample_rand")); } if (!sentry_value_is_null(dsc)) { sentry_value_t trace_id = sentry_value_get_by_key( @@ -496,7 +495,7 @@ sentry__envelope_add_transaction( sentry_value_t dsc = sentry_value_new_null(); SENTRY_WITH_SCOPE (scope) { - dsc = sentry__value_clone(scope->dynamic_sampling_context); + dsc = sentry__value_clone(sentry__scope_get_dsc(scope)); } if (!sentry_value_is_null(dsc)) { diff --git a/src/sentry_scope.c b/src/sentry_scope.c index fe83f05955..b27e098f40 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -13,6 +13,7 @@ #include "sentry_sync.h" #include "sentry_tracing.h" #include "sentry_transport.h" +#include "sentry_uuid.h" #include "sentry_value.h" #include @@ -69,6 +70,23 @@ get_client_sdk(void) return client_sdk; } +static void +generate_propagation_context(sentry_value_t propagation_context) +{ + sentry_value_set_by_key( + propagation_context, "trace", sentry_value_new_object()); + sentry_uuid_t trace_id = sentry_uuid_new_v4(); + sentry_uuid_t span_id = sentry_uuid_new_v4(); + sentry_value_set_by_key( + sentry_value_get_by_key(propagation_context, "trace"), "trace_id", + sentry__value_new_internal_uuid(&trace_id)); + sentry_value_set_by_key( + sentry_value_get_by_key(propagation_context, "trace"), "span_id", + sentry__value_new_span_uuid(&span_id)); + sentry__generate_sample_rand( + sentry_value_get_by_key(propagation_context, "trace")); +} + static void init_scope(sentry_scope_t *scope) { @@ -339,6 +357,28 @@ sentry_local_scope_new(void) return scope; } +void +sentry__scope_apply_options(sentry_scope_t *scope, sentry_options_t *options) +{ + if (options->sdk_name) { + sentry_value_t sdk_name = sentry_value_new_string(options->sdk_name); + sentry_value_set_by_key(scope->client_sdk, "name", sdk_name); + } + sentry_value_freeze(scope->client_sdk); + generate_propagation_context(scope->propagation_context); + sentry__scope_set_release_n( + scope, options->release, sentry__guarded_strlen(options->release)); + sentry__scope_set_environment_n(scope, options->environment, + sentry__guarded_strlen(options->environment)); + scope->attachments = options->attachments; + options->attachments = NULL; + + sentry__ringbuffer_set_max_size( + scope->breadcrumbs, options->max_breadcrumbs); + + sentry__scope_update_dsc(scope, options); +} + void sentry_scope_clear(sentry_scope_t *scope) { @@ -430,6 +470,49 @@ sentry_scope_clone(const sentry_scope_t *scope) return clone; } +sentry_value_t +sentry__scope_get_propagation_context(const sentry_scope_t *scope) +{ + return scope->propagation_context; +} + +sentry_value_t +sentry__scope_get_trace_context(const sentry_scope_t *scope) +{ + return sentry_value_get_by_key(scope->propagation_context, "trace"); +} + +void +sentry__scope_set_propagation_context( + sentry_scope_t *scope, const char *key, sentry_value_t value) +{ + sentry_value_set_by_key(scope->propagation_context, key, value); +} + +void +sentry__scope_regenerate_propagation_context(sentry_scope_t *scope) +{ + generate_propagation_context(scope->propagation_context); +} + +bool +sentry__scope_is_trace_managed(const sentry_scope_t *scope) +{ + return scope->trace_managed; +} + +void +sentry__scope_set_trace_managed(sentry_scope_t *scope, bool managed) +{ + scope->trace_managed = managed; +} + +sentry_value_t +sentry__scope_get_dsc(const sentry_scope_t *scope) +{ + return scope->dynamic_sampling_context; +} + void sentry__scope_freeze_dsc(sentry_scope_t *scope, sentry_value_t incoming) { @@ -760,6 +843,18 @@ sentry_scope_add_breadcrumb(sentry_scope_t *scope, sentry_value_t breadcrumb) } } +const sentry_ringbuffer_t * +sentry__scope_get_breadcrumbs(const sentry_scope_t *scope) +{ + return scope->breadcrumbs; +} + +sentry_value_t +sentry__scope_get_user(const sentry_scope_t *scope) +{ + return scope->user; +} + void sentry_scope_set_user(sentry_scope_t *scope, sentry_value_t user) { @@ -768,6 +863,12 @@ sentry_scope_set_user(sentry_scope_t *scope, sentry_value_t user) SENTRY_SCOPE_NOTIFY(scope, set_user, user); } +sentry_value_t +sentry__scope_get_tags(const sentry_scope_t *scope) +{ + return scope->tags; +} + void sentry_scope_set_tag(sentry_scope_t *scope, const char *key, const char *value) { @@ -807,6 +908,31 @@ sentry_scope_set_tags(sentry_scope_t *scope, sentry_value_t tags) sentry_value_decref(tags); } +void +sentry__scope_remove_tag(sentry_scope_t *scope, const char *key) +{ + if (sentry_value_remove_by_key(scope->tags, key) == 0) { + SENTRY_SCOPE_NOTIFY(scope, remove_tag, key); + } +} + +void +sentry__scope_remove_tag_n( + sentry_scope_t *scope, const char *key, size_t key_len) +{ + char *k = sentry__value_remove_and_take_key_n(scope->tags, key, key_len); + if (k) { + SENTRY_SCOPE_NOTIFY(scope, remove_tag, k); + } + sentry_free(k); +} + +sentry_value_t +sentry__scope_get_extra(const sentry_scope_t *scope) +{ + return scope->extra; +} + void sentry_scope_set_extra( sentry_scope_t *scope, const char *key, sentry_value_t value) @@ -826,6 +952,25 @@ sentry_scope_set_extra_n(sentry_scope_t *scope, const char *key, size_t key_len, } } +void +sentry__scope_remove_extra(sentry_scope_t *scope, const char *key) +{ + if (sentry_value_remove_by_key(scope->extra, key) == 0) { + SENTRY_SCOPE_NOTIFY(scope, remove_extra, key); + } +} + +void +sentry__scope_remove_extra_n( + sentry_scope_t *scope, const char *key, size_t key_len) +{ + char *k = sentry__value_remove_and_take_key_n(scope->extra, key, key_len); + if (k) { + SENTRY_SCOPE_NOTIFY(scope, remove_extra, k); + } + sentry_free(k); +} + void sentry_scope_set_attribute( sentry_scope_t *scope, const char *key, sentry_value_t attribute) @@ -847,6 +992,12 @@ sentry_scope_set_attribute_n(sentry_scope_t *scope, const char *key, sentry_value_set_by_key_n(scope->attributes, key, key_len, attribute); } +sentry_value_t +sentry__scope_get_attributes(const sentry_scope_t *scope) +{ + return scope->attributes; +} + void sentry_scope_remove_attribute(sentry_scope_t *scope, const char *key) { @@ -860,6 +1011,12 @@ sentry_scope_remove_attribute_n( sentry_value_remove_by_key_n(scope->attributes, key, key_len); } +sentry_value_t +sentry__scope_get_contexts(const sentry_scope_t *scope) +{ + return scope->contexts; +} + void sentry_scope_set_context( sentry_scope_t *scope, const char *key, sentry_value_t value) @@ -910,6 +1067,32 @@ sentry_scope_update_context_n(sentry_scope_t *scope, const char *key, SENTRY_SCOPE_NOTIFY(scope, set_context, k, value); } +void +sentry__scope_remove_context(sentry_scope_t *scope, const char *key) +{ + if (sentry_value_remove_by_key(scope->contexts, key) == 0) { + SENTRY_SCOPE_NOTIFY(scope, remove_context, key); + } +} + +void +sentry__scope_remove_context_n( + sentry_scope_t *scope, const char *key, size_t key_len) +{ + char *k + = sentry__value_remove_and_take_key_n(scope->contexts, key, key_len); + if (k) { + SENTRY_SCOPE_NOTIFY(scope, remove_context, k); + } + sentry_free(k); +} + +sentry_value_t +sentry__scope_get_fingerprint(const sentry_scope_t *scope) +{ + return scope->fingerprint; +} + void sentry__scope_set_fingerprint_va( sentry_scope_t *scope, const char *fingerprint, va_list va) @@ -988,6 +1171,12 @@ sentry_scope_remove_fingerprint(sentry_scope_t *scope) SENTRY_SCOPE_NOTIFY(scope, set_fingerprint, scope->fingerprint); } +sentry_level_t +sentry__scope_get_level(const sentry_scope_t *scope) +{ + return scope->level; +} + void sentry_scope_set_level(sentry_scope_t *scope, sentry_level_t level) { @@ -1045,6 +1234,181 @@ sentry_scope_add_attachment(sentry_scope_t *scope, sentry_value_t attachment) return attachment_id; } +sentry_transaction_t * +sentry__scope_get_transaction_object(const sentry_scope_t *scope) +{ + return scope->transaction_object; +} + +static bool +value_has_span_id(sentry_value_t value, const char *span_id) +{ + const char *value_span_id + = sentry_value_as_string(sentry_value_get_by_key(value, "span_id")); + return sentry__string_eq(value_span_id, span_id); +} + +void +sentry__scope_set_transaction_object( + sentry_scope_t *scope, sentry_transaction_t *transaction) +{ + sentry__transaction_incref(transaction); + sentry__span_decref(scope->span); + scope->span = NULL; + sentry__transaction_decref(scope->transaction_object); + scope->transaction_object = transaction; +} + +bool +sentry__scope_remove_transaction_object( + sentry_scope_t *scope, sentry_transaction_t *transaction) +{ + if (!transaction || scope->transaction_object != transaction) { + return false; + } + + scope->transaction_object = NULL; + sentry__transaction_decref(transaction); + return true; +} + +bool +sentry__scope_remove_transaction_value( + sentry_scope_t *scope, sentry_value_t transaction) +{ + const char *span_id = sentry_value_as_string( + sentry_value_get_by_key(transaction, "span_id")); + if (!scope->transaction_object + || !value_has_span_id(scope->transaction_object->inner, span_id)) { + return false; + } + + sentry_transaction_t *transaction_object = scope->transaction_object; + scope->transaction_object = NULL; + sentry__transaction_decref(transaction_object); + return true; +} + +bool +sentry__scope_restore_transaction_object( + sentry_scope_t *scope, sentry_transaction_t *transaction) +{ + if (scope->transaction_object || !transaction) { + return false; + } + + scope->transaction_object = transaction; + return true; +} + +sentry_span_t * +sentry__scope_get_span(const sentry_scope_t *scope) +{ + return scope->span; +} + +void +sentry__scope_set_span(sentry_scope_t *scope, sentry_span_t *span) +{ + sentry__span_incref(span); + sentry__transaction_decref(scope->transaction_object); + scope->transaction_object = NULL; + sentry__span_decref(scope->span); + scope->span = span; +} + +bool +sentry__scope_remove_span(sentry_scope_t *scope, sentry_span_t *span) +{ + if (!span || scope->span != span) { + return false; + } + + scope->span = NULL; + sentry__span_decref(span); + return true; +} + +bool +sentry__scope_remove_span_value(sentry_scope_t *scope, sentry_value_t span) +{ + const char *span_id + = sentry_value_as_string(sentry_value_get_by_key(span, "span_id")); + if (!scope->span || !value_has_span_id(scope->span->inner, span_id)) { + return false; + } + + sentry_span_t *scope_span = scope->span; + scope->span = NULL; + sentry__span_decref(scope_span); + return true; +} + +bool +sentry__scope_restore_span(sentry_scope_t *scope, sentry_span_t *span) +{ + if (scope->span || !span) { + return false; + } + + scope->span = span; + return true; +} + +const char * +sentry__scope_get_release(const sentry_scope_t *scope) +{ + return scope->release; +} + +void +sentry__scope_set_release_n( + sentry_scope_t *scope, const char *release, size_t release_len) +{ + sentry_free(scope->release); + scope->release = sentry__string_clone_n(release, release_len); + sentry_value_set_by_key(scope->dynamic_sampling_context, "release", + sentry_value_new_string(scope->release)); + SENTRY_SCOPE_NOTIFY(scope, set_release, scope->release); +} + +const char * +sentry__scope_get_environment(const sentry_scope_t *scope) +{ + return scope->environment; +} + +void +sentry__scope_set_environment_n( + sentry_scope_t *scope, const char *environment, size_t environment_len) +{ + sentry_free(scope->environment); + scope->environment = sentry__string_clone_n(environment, environment_len); + sentry_value_set_by_key(scope->dynamic_sampling_context, "environment", + sentry_value_new_string(scope->environment)); + SENTRY_SCOPE_NOTIFY(scope, set_environment, scope->environment); +} + +const char * +sentry__scope_get_transaction(const sentry_scope_t *scope) +{ + return scope->transaction; +} + +void +sentry__scope_set_transaction_n( + sentry_scope_t *scope, const char *transaction, size_t transaction_len) +{ + sentry_free(scope->transaction); + scope->transaction = sentry__string_clone_n(transaction, transaction_len); + + if (scope->transaction_object) { + sentry_transaction_set_name_n( + scope->transaction_object, transaction, transaction_len); + } + SENTRY_SCOPE_NOTIFY(scope, set_transaction, scope->transaction); +} + sentry_uuid_t sentry_scope_attach_file(sentry_scope_t *scope, const char *path) { diff --git a/src/sentry_scope.h b/src/sentry_scope.h index aa758a824b..4da36822cf 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -123,6 +123,9 @@ void sentry__scope_unlock(void); */ void sentry__scope_cleanup(void); +void sentry__scope_apply_options( + sentry_scope_t *scope, sentry_options_t *options); + /** * Frees the scope if it is a one-shot local scope. */ @@ -145,13 +148,75 @@ void sentry__scope_apply_to_event(const sentry_scope_t *scope, const sentry_options_t *options, sentry_value_t event, sentry_scope_mode_t mode); +const char *sentry__scope_get_release(const sentry_scope_t *scope); +void sentry__scope_set_release_n( + sentry_scope_t *scope, const char *release, size_t release_len); + +const char *sentry__scope_get_environment(const sentry_scope_t *scope); +void sentry__scope_set_environment_n( + sentry_scope_t *scope, const char *environment, size_t environment_len); + +const char *sentry__scope_get_transaction(const sentry_scope_t *scope); +void sentry__scope_set_transaction_n( + sentry_scope_t *scope, const char *transaction, size_t transaction_len); + +sentry_value_t sentry__scope_get_fingerprint(const sentry_scope_t *scope); void sentry__scope_set_fingerprint_va( sentry_scope_t *scope, const char *fingerprint, va_list va); void sentry__scope_set_fingerprint_nva(sentry_scope_t *scope, const char *fingerprint, size_t fingerprint_len, va_list va); +sentry_value_t sentry__scope_get_user(const sentry_scope_t *scope); +sentry_level_t sentry__scope_get_level(const sentry_scope_t *scope); sentry_value_t sentry__scope_add_attachment( sentry_scope_t *scope, sentry_value_t attachment); +sentry_value_t sentry__scope_get_tags(const sentry_scope_t *scope); +void sentry__scope_remove_tag(sentry_scope_t *scope, const char *key); +void sentry__scope_remove_tag_n( + sentry_scope_t *scope, const char *key, size_t key_len); + +sentry_value_t sentry__scope_get_extra(const sentry_scope_t *scope); +void sentry__scope_remove_extra(sentry_scope_t *scope, const char *key); +void sentry__scope_remove_extra_n( + sentry_scope_t *scope, const char *key, size_t key_len); + +sentry_value_t sentry__scope_get_attributes(const sentry_scope_t *scope); + +sentry_value_t sentry__scope_get_contexts(const sentry_scope_t *scope); +void sentry__scope_remove_context(sentry_scope_t *scope, const char *key); +void sentry__scope_remove_context_n( + sentry_scope_t *scope, const char *key, size_t key_len); + +sentry_value_t sentry__scope_get_propagation_context( + const sentry_scope_t *scope); +sentry_value_t sentry__scope_get_trace_context(const sentry_scope_t *scope); +void sentry__scope_set_propagation_context( + sentry_scope_t *scope, const char *key, sentry_value_t value); +void sentry__scope_regenerate_propagation_context(sentry_scope_t *scope); + +const sentry_ringbuffer_t *sentry__scope_get_breadcrumbs( + const sentry_scope_t *scope); + +sentry_transaction_t *sentry__scope_get_transaction_object( + const sentry_scope_t *scope); +void sentry__scope_set_transaction_object( + sentry_scope_t *scope, sentry_transaction_t *transaction); +bool sentry__scope_remove_transaction_object( + sentry_scope_t *scope, sentry_transaction_t *transaction); +bool sentry__scope_remove_transaction_value( + sentry_scope_t *scope, sentry_value_t transaction); +bool sentry__scope_restore_transaction_object( + sentry_scope_t *scope, sentry_transaction_t *transaction); + +sentry_span_t *sentry__scope_get_span(const sentry_scope_t *scope); +void sentry__scope_set_span(sentry_scope_t *scope, sentry_span_t *span); +bool sentry__scope_remove_span(sentry_scope_t *scope, sentry_span_t *span); +bool sentry__scope_remove_span_value( + sentry_scope_t *scope, sentry_value_t span); +bool sentry__scope_restore_span(sentry_scope_t *scope, sentry_span_t *span); + +bool sentry__scope_is_trace_managed(const sentry_scope_t *scope); +void sentry__scope_set_trace_managed(sentry_scope_t *scope, bool managed); /** * These are convenience macros to automatically lock/unlock the global scope @@ -211,6 +276,8 @@ void sentry__scope_end_notify(sentry_scope_t *scope); sentry__scope_end_notify(scope); \ } while (0) +sentry_value_t sentry__scope_get_dsc(const sentry_scope_t *scope); + /** * Rebuilds the scope's dynamic sampling context (DSC) from the SDK options * and the current propagation context. The previous DSC is discarded. diff --git a/src/sentry_session.c b/src/sentry_session.c index 182dfa92c5..49b90415cc 100644 --- a/src/sentry_session.c +++ b/src/sentry_session.c @@ -50,8 +50,9 @@ status_from_string(const char *status) sentry_session_t * sentry__session_new(const sentry_scope_t *scope) { - char *release = sentry__string_clone(scope->release); - char *environment = sentry__string_clone(scope->environment); + char *release = sentry__string_clone(sentry__scope_get_release(scope)); + char *environment + = sentry__string_clone(sentry__scope_get_environment(scope)); if (!release) { sentry_free(environment); @@ -219,7 +220,8 @@ sentry_start_session(void) if (options) { options->session = sentry__session_new(scope); if (options->session) { - sentry__session_sync_user(options->session, scope->user, + sentry__session_sync_user(options->session, + sentry__scope_get_user(scope), options->run ? options->run->installation_id : NULL); sentry__run_write_session(options->run, options->session); } diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c index 8b94d36e9f..a812942208 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -96,22 +96,18 @@ transaction_context_new_n(sentry_slice_t name, sentry_slice_t operation) sentry_value_new_string_n(name.ptr, name.len)); SENTRY_WITH_SCOPE_MUT (scope) { - if (!scope->trace_managed - && !sentry_value_is_null( - sentry_value_get_by_key(scope->propagation_context, "trace"))) { + sentry_value_t trace_context = sentry__scope_get_trace_context(scope); + if (!sentry__scope_is_trace_managed(scope) + && !sentry_value_is_null(trace_context)) { // The trace is managed from outside, so we use the propagation // context as the trace source for this transaction. This means that // either a downstream SDK or the user manages trace life-cycles. sentry_value_set_by_key(transaction_context, "trace_id", - sentry__value_clone(sentry_value_get_by_key( - sentry_value_get_by_key( - scope->propagation_context, "trace"), - "trace_id"))); + sentry__value_clone( + sentry_value_get_by_key(trace_context, "trace_id"))); sentry_value_set_by_key(transaction_context, "parent_span_id", - sentry__value_clone(sentry_value_get_by_key( - sentry_value_get_by_key( - scope->propagation_context, "trace"), - "parent_span_id"))); + sentry__value_clone( + sentry_value_get_by_key(trace_context, "parent_span_id"))); } } @@ -965,7 +961,7 @@ sentry__span_iter_headers(sentry_value_t span, SENTRY_WITH_SCOPE (scope) { sentry__value_foreach_key_value( - scope->dynamic_sampling_context, append_baggage_member, &sb); + sentry__scope_get_dsc(scope), append_baggage_member, &sb); } char *baggage = sentry__stringbuilder_into_string(&sb); @@ -1019,13 +1015,13 @@ save_active_trace(void) { saved_trace_t s = { 0 }; SENTRY_WITH_SCOPE (scope) { - if (scope->span) { - sentry__span_incref(scope->span); - s.saved_span = scope->span; + s.saved_span = sentry__scope_get_span(scope); + if (s.saved_span) { + sentry__span_incref(s.saved_span); } - if (scope->transaction_object) { - sentry__transaction_incref(scope->transaction_object); - s.saved_tx_obj = scope->transaction_object; + s.saved_tx_obj = sentry__scope_get_transaction_object(scope); + if (s.saved_tx_obj) { + sentry__transaction_incref(s.saved_tx_obj); } } s.active_tx = s.saved_span && s.saved_span->transaction @@ -1041,12 +1037,10 @@ static void restore_active_trace(saved_trace_t *s) { SENTRY_WITH_SCOPE_MUT (scope) { - if (!scope->span && s->saved_span) { - scope->span = s->saved_span; + if (sentry__scope_restore_span(scope, s->saved_span)) { s->saved_span = NULL; } - if (!scope->transaction_object && s->saved_tx_obj) { - scope->transaction_object = s->saved_tx_obj; + if (sentry__scope_restore_transaction_object(scope, s->saved_tx_obj)) { s->saved_tx_obj = NULL; } } diff --git a/tests/unit/test_logs.c b/tests/unit/test_logs.c index 1e7cfcb945..13182ff536 100644 --- a/tests/unit/test_logs.c +++ b/tests/unit/test_logs.c @@ -768,9 +768,7 @@ SENTRY_TEST(scope_capture_log_trace_id) TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( captured_log, "trace_id")), sentry_value_as_string(sentry_value_get_by_key( - sentry_value_get_by_key( - global_scope->propagation_context, "trace"), - "trace_id"))); + sentry__scope_get_trace_context(global_scope), "trace_id"))); } sentry_scope_free(scope); diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 64d87d5f53..28e595c291 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -123,8 +123,8 @@ SENTRY_TEST(scope_update_context) sentry_update_context("device", device); SENTRY_WITH_SCOPE (scope) { - sentry_value_t ctx - = sentry_value_get_by_key(scope->contexts, "device"); + sentry_value_t ctx = sentry_value_get_by_key( + sentry__scope_get_contexts(scope), "device"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(ctx, "model")), "Xbox Series X"); @@ -143,8 +143,8 @@ SENTRY_TEST(scope_update_context) sentry_update_context("device", extra); SENTRY_WITH_SCOPE (scope) { - sentry_value_t ctx - = sentry_value_get_by_key(scope->contexts, "device"); + sentry_value_t ctx = sentry_value_get_by_key( + sentry__scope_get_contexts(scope), "device"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(ctx, "model")), "PC"); @@ -166,8 +166,8 @@ SENTRY_TEST(scope_update_context) sentry_value_set_by_key(os, "name", sentry_value_new_string("SteamOS")); sentry_scope_update_context(local_scope, "os", os); - sentry_value_t ctx - = sentry_value_get_by_key(local_scope->contexts, "os"); + sentry_value_t ctx = sentry_value_get_by_key( + sentry__scope_get_contexts(local_scope), "os"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(ctx, "name")), "SteamOS"); @@ -178,7 +178,8 @@ SENTRY_TEST(scope_update_context) sentry_value_set_by_key(os2, "version", sentry_value_new_string("6.1")); sentry_scope_update_context(local_scope, "os", os2); - ctx = sentry_value_get_by_key(local_scope->contexts, "os"); + ctx = sentry_value_get_by_key( + sentry__scope_get_contexts(local_scope), "os"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(ctx, "name")), "Linux"); @@ -1095,7 +1096,7 @@ SENTRY_TEST(scope_clone) // scope values must not be corrupted by before_send modifications SENTRY_WITH_SCOPE (scope) { sentry_value_t scope_gpu - = sentry_value_get_by_key(scope->contexts, "gpu"); + = sentry_value_get_by_key(sentry__scope_get_contexts(scope), "gpu"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(scope_gpu, "name")), "original"); @@ -1103,7 +1104,7 @@ SENTRY_TEST(scope_clone) sentry_value_get_by_key(scope_gpu, "injected"))); sentry_value_t scope_data - = sentry_value_get_by_key(scope->extra, "data"); + = sentry_value_get_by_key(sentry__scope_get_extra(scope), "data"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(scope_data, "key")), "original"); @@ -1111,12 +1112,13 @@ SENTRY_TEST(scope_clone) sentry_value_get_by_key(scope_data, "injected"))); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - scope->user, "username")), + sentry__scope_get_user(scope), "username")), "original"); - TEST_CHECK(sentry_value_is_null( - sentry_value_get_by_key(scope->user, "injected"))); + TEST_CHECK(sentry_value_is_null(sentry_value_get_by_key( + sentry__scope_get_user(scope), "injected"))); - TEST_CHECK_INT_EQUAL(sentry_value_get_length(scope->fingerprint), 2); + TEST_CHECK_INT_EQUAL( + sentry_value_get_length(sentry__scope_get_fingerprint(scope)), 2); } sentry_close(); @@ -1133,7 +1135,7 @@ SENTRY_TEST(scope_global_attributes) sentry_set_attribute("valid_key", valid_attr); SENTRY_WITH_SCOPE (scope) { - sentry_value_t attributes = scope->attributes; + sentry_value_t attributes = sentry__scope_get_attributes(scope); sentry_value_t retrieved_attr = sentry_value_get_by_key(attributes, "valid_key"); @@ -1155,7 +1157,7 @@ SENTRY_TEST(scope_global_attributes) sentry_set_attribute("invalid_no_value", invalid_attr_no_value); SENTRY_WITH_SCOPE (scope) { - sentry_value_t attributes = scope->attributes; + sentry_value_t attributes = sentry__scope_get_attributes(scope); sentry_value_t retrieved_attr = sentry_value_get_by_key(attributes, "invalid_no_value"); @@ -1171,7 +1173,7 @@ SENTRY_TEST(scope_global_attributes) sentry_set_attribute("invalid_no_type", invalid_attr_no_type); SENTRY_WITH_SCOPE (scope) { - sentry_value_t attributes = scope->attributes; + sentry_value_t attributes = sentry__scope_get_attributes(scope); sentry_value_t retrieved_attr = sentry_value_get_by_key(attributes, "invalid_no_type"); @@ -1183,7 +1185,7 @@ SENTRY_TEST(scope_global_attributes) sentry_remove_attribute("valid_key"); SENTRY_WITH_SCOPE (scope) { - sentry_value_t attributes = scope->attributes; + sentry_value_t attributes = sentry__scope_get_attributes(scope); sentry_value_t retrieved_attr = sentry_value_get_by_key(attributes, "valid_key"); @@ -1197,7 +1199,7 @@ SENTRY_TEST(scope_global_attributes) sentry_set_attribute_n("key_n", 5, attr_n); SENTRY_WITH_SCOPE (scope) { - sentry_value_t attributes = scope->attributes; + sentry_value_t attributes = sentry__scope_get_attributes(scope); sentry_value_t retrieved_attr = sentry_value_get_by_key(attributes, "key_n"); @@ -1232,7 +1234,7 @@ SENTRY_TEST(scope_local_attributes) sentry_value_new_attribute(sentry_value_new_string("global"), NULL)); SENTRY_WITH_SCOPE (global_scope) { - sentry_value_t attributes = global_scope->attributes; + sentry_value_t attributes = sentry__scope_get_attributes(global_scope); // Verify global attributes are set TEST_CHECK_STRING_EQUAL( @@ -1260,7 +1262,8 @@ SENTRY_TEST(scope_local_attributes) sentry_scope_set_attribute(local_scope, "scope", sentry_value_new_attribute(sentry_value_new_string("local"), NULL)); - sentry_value_t local_attributes = local_scope->attributes; + sentry_value_t local_attributes + = sentry__scope_get_attributes(local_scope); // Verify local attributes are set TEST_CHECK_STRING_EQUAL( @@ -1277,7 +1280,8 @@ SENTRY_TEST(scope_local_attributes) "local"); // Verify global scope still has its own attributes - sentry_value_t global_attributes = global_scope->attributes; + sentry_value_t global_attributes + = sentry__scope_get_attributes(global_scope); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key( sentry_value_get_by_key(global_attributes, "all"), "value")), @@ -1294,7 +1298,7 @@ SENTRY_TEST(scope_local_attributes) sentry_remove_attribute("all"); SENTRY_WITH_SCOPE (scope) { - sentry_value_t attributes = scope->attributes; + sentry_value_t attributes = sentry__scope_get_attributes(scope); TEST_CHECK( sentry_value_is_null(sentry_value_get_by_key(attributes, "all"))); // Other attributes should still exist @@ -1310,7 +1314,8 @@ SENTRY_TEST(scope_local_attributes) sentry_scope_set_attribute_n(local_scope, "test_key", 8, sentry_value_new_attribute(sentry_value_new_int32(100), "percent")); - sentry_value_t local_attributes = local_scope->attributes; + sentry_value_t local_attributes + = sentry__scope_get_attributes(local_scope); sentry_value_t attr = sentry_value_get_by_key(local_attributes, "test_key"); @@ -1342,7 +1347,8 @@ SENTRY_TEST(scope_local_attributes) invalid_attr, "type", sentry_value_new_string("string")); sentry_scope_set_attribute(local_scope, "invalid", invalid_attr); - sentry_value_t local_attributes = local_scope->attributes; + sentry_value_t local_attributes + = sentry__scope_get_attributes(local_scope); TEST_CHECK(sentry_value_is_null( sentry_value_get_by_key(local_attributes, "invalid"))); @@ -1647,7 +1653,7 @@ SENTRY_TEST(scope_observer_clear) sentry_scope_clear(scope); TEST_CHECK(d.was_cleared); TEST_CHECK_INT_EQUAL(scope->num_observers, 1); - TEST_CHECK(sentry_value_get_length(scope->tags) == 0); + TEST_CHECK(sentry_value_get_length(sentry__scope_get_tags(scope)) == 0); d.was_called = false; sentry_scope_set_tag(scope, "after", "clear"); @@ -1675,7 +1681,7 @@ SENTRY_TEST(scope_observer_clear) TEST_CHECK(observer_data.was_cleared); TEST_CHECK_INT_EQUAL(scope->is_notifying, 0); TEST_CHECK_INT_EQUAL(scope->num_observers, 1); - TEST_CHECK(sentry_value_get_length(scope->tags) == 0); + TEST_CHECK(sentry_value_get_length(sentry__scope_get_tags(scope)) == 0); sentry_value_decref(observer_data.tags); sentry_scope_free(scope); @@ -2398,7 +2404,8 @@ SENTRY_TEST(scope_set_attribute_invalid_decref_value) TEST_CHECK_INT_EQUAL(sentry_value_refcount(no_type), 1); sentry_value_decref(no_type); - TEST_CHECK_INT_EQUAL(sentry_value_get_length(scope->attributes), 0); + TEST_CHECK_INT_EQUAL( + sentry_value_get_length(sentry__scope_get_attributes(scope)), 0); sentry_scope_free(scope); } @@ -2415,7 +2422,8 @@ SENTRY_TEST(scope_set_attribute_null_key_decref_value) TEST_CHECK_INT_EQUAL(sentry_value_refcount(v), 1); sentry_value_decref(v); - TEST_CHECK_INT_EQUAL(sentry_value_get_length(scope->attributes), 0); + TEST_CHECK_INT_EQUAL( + sentry_value_get_length(sentry__scope_get_attributes(scope)), 0); sentry_scope_free(scope); } @@ -2436,7 +2444,8 @@ SENTRY_TEST(scope_ownership) static size_t scope_breadcrumb_count(const sentry_scope_t *scope) { - sentry_value_t breadcrumbs = sentry__ringbuffer_to_list(scope->breadcrumbs); + sentry_value_t breadcrumbs + = sentry__ringbuffer_to_list(sentry__scope_get_breadcrumbs(scope)); size_t count = sentry_value_get_length(breadcrumbs); sentry_value_decref(breadcrumbs); return count; @@ -2458,10 +2467,10 @@ SENTRY_TEST(scope_clone_independence) TEST_CHECK(!clone->one_shot); // The clone carries over the source's data. - TEST_CHECK_STRING_EQUAL( - sentry_value_as_string(sentry_value_get_by_key(clone->tags, "shared")), + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( + sentry__scope_get_tags(clone), "shared")), "original"); - TEST_CHECK(clone->level == SENTRY_LEVEL_WARNING); + TEST_CHECK(sentry__scope_get_level(clone) == SENTRY_LEVEL_WARNING); TEST_CHECK_INT_EQUAL(scope_breadcrumb_count(clone), 1); // Mutating the clone does not affect the source, and vice versa. @@ -2470,11 +2479,11 @@ SENTRY_TEST(scope_clone_independence) sentry_scope_add_breadcrumb( clone, sentry_value_new_breadcrumb(NULL, "second")); - TEST_CHECK_STRING_EQUAL( - sentry_value_as_string(sentry_value_get_by_key(scope->tags, "shared")), + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( + sentry__scope_get_tags(scope), "shared")), "original"); TEST_CHECK(sentry_value_is_null( - sentry_value_get_by_key(scope->tags, "clone_only"))); + sentry_value_get_by_key(sentry__scope_get_tags(scope), "clone_only"))); TEST_CHECK_INT_EQUAL(scope_breadcrumb_count(scope), 1); TEST_CHECK_INT_EQUAL(scope_breadcrumb_count(clone), 2); @@ -2509,23 +2518,26 @@ SENTRY_TEST(scope_clone_preserves_data) sentry_value_decref( sentry__attachments_remove(scope->attachments, &attachment_id)); - TEST_CHECK_STRING_EQUAL( - sentry_value_as_string(sentry_value_get_by_key(clone->tags, "tag_key")), + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( + sentry__scope_get_tags(clone), "tag_key")), "tag_value"); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - clone->contexts, "device")), + sentry__scope_get_contexts(clone), "device")), "Xbox"); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - clone->user, "username")), + sentry__scope_get_user(clone), "username")), "alice"); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - clone->extra, "extra_key")), + sentry__scope_get_extra(clone), "extra_key")), "extra_value"); - TEST_CHECK_INT_EQUAL(sentry_value_get_length(clone->fingerprint), 2); - TEST_CHECK(clone->level == SENTRY_LEVEL_WARNING); + TEST_CHECK_INT_EQUAL( + sentry_value_get_length(sentry__scope_get_fingerprint(clone)), 2); + TEST_CHECK(sentry__scope_get_level(clone) == SENTRY_LEVEL_WARNING); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key( - sentry_value_get_by_key(clone->attributes, "attr_key"), "value")), + sentry_value_get_by_key( + sentry__scope_get_attributes(clone), "attr_key"), + "value")), "attr_value"); TEST_CHECK_INT_EQUAL(scope_breadcrumb_count(clone), 1); @@ -2559,13 +2571,13 @@ SENTRY_TEST(scope_clone_shares_span) sentry_scope_t *clone = NULL; sentry_transaction_t *scope_txn = NULL; SENTRY_WITH_SCOPE (scope) { - scope_txn = scope->transaction_object; + scope_txn = sentry__scope_get_transaction_object(scope); clone = sentry_scope_clone(scope); } // The active transaction is shared by reference, not dropped or duplicated. TEST_CHECK(scope_txn != NULL); - TEST_CHECK(clone->transaction_object == scope_txn); + TEST_CHECK(sentry__scope_get_transaction_object(clone) == scope_txn); // The shared reference keeps the transaction alive for the original: the // clone can be freed and the transaction still finished safely. @@ -2584,9 +2596,9 @@ SENTRY_TEST(scope_clear) // Clearing a scope must keep trace propagation data intact, including the // dynamic sampling context. - sentry_value_set_by_key( - scope->propagation_context, "marker", sentry_value_new_string("keep")); - sentry_value_set_by_key(scope->dynamic_sampling_context, "marker", + sentry__scope_set_propagation_context( + scope, "marker", sentry_value_new_string("keep")); + sentry_value_set_by_key(sentry__scope_get_dsc(scope), "marker", sentry_value_new_string("keep")); sentry_scope_set_tag(scope, "tag", "value"); @@ -2604,35 +2616,39 @@ SENTRY_TEST(scope_clear) sentry_scope_add_breadcrumb( scope, sentry_value_new_breadcrumb(NULL, "crumb")); - TEST_CHECK(sentry_value_get_length(scope->tags) == 1); - TEST_CHECK(sentry_value_get_length(scope->attributes) == 1); - TEST_CHECK(!sentry_value_is_null(scope->user)); + TEST_CHECK(sentry_value_get_length(sentry__scope_get_tags(scope)) == 1); + TEST_CHECK( + sentry_value_get_length(sentry__scope_get_attributes(scope)) == 1); + TEST_CHECK(!sentry_value_is_null(sentry__scope_get_user(scope))); sentry_scope_clear(scope); // Everything is reset to the state of a fresh scope. - TEST_CHECK(sentry_value_get_length(scope->tags) == 0); - TEST_CHECK(sentry_value_get_length(scope->extra) == 0); - TEST_CHECK(sentry_value_get_length(scope->contexts) == 0); - TEST_CHECK(sentry_value_get_length(scope->attributes) == 0); - TEST_CHECK(sentry_value_is_null(scope->user)); - TEST_CHECK(sentry_value_is_null(scope->fingerprint)); - TEST_CHECK_INT_EQUAL(scope->level, SENTRY_LEVEL_ERROR); - sentry_value_t crumbs = sentry__ringbuffer_to_list(scope->breadcrumbs); + TEST_CHECK(sentry_value_get_length(sentry__scope_get_tags(scope)) == 0); + TEST_CHECK(sentry_value_get_length(sentry__scope_get_extra(scope)) == 0); + TEST_CHECK(sentry_value_get_length(sentry__scope_get_contexts(scope)) == 0); + TEST_CHECK( + sentry_value_get_length(sentry__scope_get_attributes(scope)) == 0); + TEST_CHECK(sentry_value_is_null(sentry__scope_get_user(scope))); + TEST_CHECK(sentry_value_is_null(sentry__scope_get_fingerprint(scope))); + TEST_CHECK_INT_EQUAL(sentry__scope_get_level(scope), SENTRY_LEVEL_ERROR); + sentry_value_t crumbs + = sentry__ringbuffer_to_list(sentry__scope_get_breadcrumbs(scope)); TEST_CHECK(sentry_value_get_length(crumbs) == 0); sentry_value_decref(crumbs); // ... except the trace, which is preserved. - TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - scope->propagation_context, "marker")), + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key( + sentry__scope_get_propagation_context(scope), "marker")), "keep"); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - scope->dynamic_sampling_context, "marker")), + sentry__scope_get_dsc(scope), "marker")), "keep"); // The cleared scope is still usable. sentry_scope_set_tag(scope, "after", "clear"); - TEST_CHECK(sentry_value_get_length(scope->tags) == 1); + TEST_CHECK(sentry_value_get_length(sentry__scope_get_tags(scope)) == 1); sentry_scope_free(scope); sentry_close(); @@ -2825,8 +2841,8 @@ SENTRY_TEST(scope_capture_user_owned) // The scope was applied but not freed, so reading and reusing it is safe // (a use-after-free here would trip the sanitizers). - TEST_CHECK_STRING_EQUAL( - sentry_value_as_string(sentry_value_get_by_key(scope->tags, "run")), + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( + sentry__scope_get_tags(scope), "run")), "first"); sentry_scope_set_tag(scope, "run", "second"); diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index ae1cb9588a..31fc613c28 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -852,7 +852,7 @@ SENTRY_TEST(trace_finish) // Scope still points at the (finished) span so a subsequent crash event // inherits its trace context. SENTRY_WITH_SCOPE (scope) { - TEST_CHECK(scope->span != NULL); + TEST_CHECK(sentry__scope_get_span(scope) != NULL); } sentry__span_decref(grand); @@ -934,13 +934,13 @@ SENTRY_TEST(discard_transaction) sentry_set_transaction_object(tx); SENTRY_WITH_SCOPE (scope) { - TEST_CHECK(scope->transaction_object == tx); + TEST_CHECK(sentry__scope_get_transaction_object(scope) == tx); } sentry_transaction_discard(tx); SENTRY_WITH_SCOPE (scope) { - TEST_CHECK(scope->transaction_object == NULL); + TEST_CHECK(sentry__scope_get_transaction_object(scope) == NULL); } sentry_close(); @@ -970,13 +970,13 @@ SENTRY_TEST(discard_span) sentry_set_span(span); SENTRY_WITH_SCOPE (scope) { - TEST_CHECK(scope->span == span); + TEST_CHECK(sentry__scope_get_span(scope) == span); } sentry_span_discard(span); SENTRY_WITH_SCOPE (scope) { - TEST_CHECK(scope->span == NULL); + TEST_CHECK(sentry__scope_get_span(scope) == NULL); } TEST_CHECK_INT_EQUAL( sentry_value_get_length(sentry_value_get_by_key(tx->inner, "spans")), @@ -1566,7 +1566,7 @@ SENTRY_TEST(set_trace) SENTRY_WITH_SCOPE (scope) { sentry_value_t propagation_trace_context - = sentry_value_get_by_key(scope->propagation_context, "trace"); + = sentry__scope_get_trace_context(scope); TEST_CHECK(!sentry_value_is_null(propagation_trace_context)); CHECK_STRING_PROPERTY(propagation_trace_context, "type", "trace"); @@ -2428,8 +2428,7 @@ SENTRY_TEST(strict_continuation_no_baggage_forks) SENTRY_WITH_SCOPE (scope) { const char *scope_trace_id = sentry_value_as_string(sentry_value_get_by_key( - sentry_value_get_by_key(scope->propagation_context, "trace"), - "trace_id")); + sentry__scope_get_trace_context(scope), "trace_id")); TEST_CHECK(strcmp(scope_trace_id, UPSTREAM_TRACE_ID) != 0); TEST_CHECK_STRING_EQUAL(scope_trace_id, trace_id); } @@ -2477,7 +2476,7 @@ SENTRY_TEST(set_trace_rebuilds_dsc_sample_rand) double init_sample_rand = 0.0; SENTRY_WITH_SCOPE (scope) { init_sample_rand = sentry_value_as_double(sentry_value_get_by_key( - scope->dynamic_sampling_context, "sample_rand")); + sentry__scope_get_dsc(scope), "sample_rand")); } sentry_set_trace("11112222333344445555666677778888", "1234567812345678"); @@ -2485,7 +2484,7 @@ SENTRY_TEST(set_trace_rebuilds_dsc_sample_rand) double new_sample_rand = -1.0; SENTRY_WITH_SCOPE (scope) { new_sample_rand = sentry_value_as_double(sentry_value_get_by_key( - scope->dynamic_sampling_context, "sample_rand")); + sentry__scope_get_dsc(scope), "sample_rand")); } // sample_rand is regenerated for the new trace, so the DSC must reflect // the fresh value, not the init-time one. From 7c6273d27fbe793703f147b7116ed7d47bc1c0d2 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 27 Jul 2026 15:54:04 +0200 Subject: [PATCH 02/22] WIP: observers --- src/sentry_scope.c | 12 ++++++++++++ src/sentry_scope.h | 2 ++ tests/unit/test_scope.c | 26 ++++++++++---------------- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/sentry_scope.c b/src/sentry_scope.c index b27e098f40..008b4a10ba 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -681,6 +681,18 @@ sentry__scope_get_span_or_transaction(void) } return result; } + +bool +sentry__scope_has_observers(const sentry_scope_t *scope) +{ + return scope->num_observers > 0; +} + +bool +sentry__scope_is_one_shot(const sentry_scope_t *scope) +{ + return scope->one_shot; +} #endif void diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 4da36822cf..4bc100ffc9 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -313,4 +313,6 @@ void sentry__scope_capture_envelope(sentry_scope_t *scope, // this is only used in unit tests #ifdef SENTRY_UNITTEST sentry_value_t sentry__scope_get_span_or_transaction(void); +bool sentry__scope_has_observers(const sentry_scope_t *scope); +bool sentry__scope_is_one_shot(const sentry_scope_t *scope); #endif diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 28e595c291..1723cfa0e9 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -1652,7 +1652,7 @@ SENTRY_TEST(scope_observer_clear) sentry_scope_clear(scope); TEST_CHECK(d.was_cleared); - TEST_CHECK_INT_EQUAL(scope->num_observers, 1); + TEST_CHECK(sentry__scope_has_observers(scope)); TEST_CHECK(sentry_value_get_length(sentry__scope_get_tags(scope)) == 0); d.was_called = false; @@ -1679,8 +1679,7 @@ SENTRY_TEST(scope_observer_clear) sentry_scope_set_tag(scope, "during", "notify"); TEST_CHECK(observer_data.was_called); TEST_CHECK(observer_data.was_cleared); - TEST_CHECK_INT_EQUAL(scope->is_notifying, 0); - TEST_CHECK_INT_EQUAL(scope->num_observers, 1); + TEST_CHECK(sentry__scope_has_observers(scope)); TEST_CHECK(sentry_value_get_length(sentry__scope_get_tags(scope)) == 0); sentry_value_decref(observer_data.tags); @@ -1694,12 +1693,10 @@ SENTRY_TEST(scope_observer_null) SENTRY_WITH_SCOPE_MUT (scope) { TEST_CHECK(!sentry__scope_add_observer(scope, NULL)); - TEST_CHECK_INT_EQUAL(scope->num_observers, 0); - TEST_CHECK(scope->observers == NULL); + TEST_CHECK(!sentry__scope_has_observers(scope)); sentry__scope_remove_observer(scope, NULL); - TEST_CHECK_INT_EQUAL(scope->num_observers, 0); - TEST_CHECK(scope->observers == NULL); + TEST_CHECK(!sentry__scope_has_observers(scope)); } test_observer_data_t d = { .tags = sentry_value_new_null() }; @@ -1757,8 +1754,7 @@ SENTRY_TEST(scope_observer_multiple) d2.was_called = false; SENTRY_WITH_SCOPE_MUT (scope) { sentry__scope_remove_observer(scope, observer2); - TEST_CHECK_INT_EQUAL(scope->num_observers, 1); - TEST_CHECK(scope->observers != NULL); + TEST_CHECK(sentry__scope_has_observers(scope)); } sentry_set_tag("multi", "again"); @@ -1770,8 +1766,7 @@ SENTRY_TEST(scope_observer_multiple) SENTRY_WITH_SCOPE_MUT (scope) { sentry__scope_remove_observer(scope, observer1); - TEST_CHECK_INT_EQUAL(scope->num_observers, 0); - TEST_CHECK(scope->observers == NULL); + TEST_CHECK(!sentry__scope_has_observers(scope)); } sentry_value_decref(d1.tags); @@ -1836,14 +1831,13 @@ SENTRY_TEST(scope_observer_mutate) SENTRY_WITH_SCOPE_MUT (scope) { TEST_CHECK(sentry__scope_add_observer(scope, observer4)); - TEST_CHECK_INT_EQUAL(scope->num_observers, 1); + TEST_CHECK(sentry__scope_has_observers(scope)); } sentry_set_tag("self", "remove"); TEST_CHECK(d4.was_called); SENTRY_WITH_SCOPE_MUT (scope) { - TEST_CHECK_INT_EQUAL(scope->num_observers, 0); - TEST_CHECK(scope->observers == NULL); + TEST_CHECK(!sentry__scope_has_observers(scope)); } sentry_value_decref(d1.tags); @@ -2433,11 +2427,11 @@ SENTRY_TEST(scope_ownership) // `sentry_local_scope_new` makes a one-shot scope, `sentry_scope_new` does // not. sentry_scope_t *local_scope = sentry_local_scope_new(); - TEST_CHECK(local_scope->one_shot); + TEST_CHECK(sentry__scope_is_one_shot(local_scope)); sentry_scope_free(local_scope); sentry_scope_t *user_scope = sentry_scope_new(); - TEST_CHECK(!user_scope->one_shot); + TEST_CHECK(!sentry__scope_is_one_shot(user_scope)); sentry_scope_free(user_scope); } From 9a37cb8f6aa7681c26dfbbceb5976e50fdbc8e4c Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 5 Aug 2026 16:15:54 +0200 Subject: [PATCH 03/22] feat(sync): add platform rwlock primitive Add a non-recursive sentry_rwlock_t abstraction backed by platform reader/writer locks. Cover static and dynamic initialization, reader sharing, writer exclusion, and mixed read/write stress in unit tests. --- src/sentry_sync.h | 184 ++++++++++++++++++++++++++++ tests/unit/test_sync.c | 270 +++++++++++++++++++++++++++++++++++++++++ tests/unit/tests.inc | 7 ++ 3 files changed, 461 insertions(+) diff --git a/src/sentry_sync.h b/src/sentry_sync.h index 3e28a28360..39cfbe81f5 100644 --- a/src/sentry_sync.h +++ b/src/sentry_sync.h @@ -9,6 +9,7 @@ // This is a NOP for platforms that support static mutex initialization. #define SENTRY__MUTEX_INIT_DYN_ONCE(Mutex) ((void)0) +#define SENTRY__RWLOCK_INIT_DYN_ONCE(Rwlock) ((void)0) #ifdef _MSC_VER # define THREAD_FUNCTION_API __stdcall @@ -191,6 +192,137 @@ typedef struct sentry__winmutex_s sentry_mutex_t; # define sentry__mutex_free(Lock) \ DeleteCriticalSection(&(Lock)->critical_section) +# if _WIN32_WINNT >= 0x0600 +typedef SRWLOCK sentry_rwlock_t; +# define SENTRY__RWLOCK_INIT SRWLOCK_INIT +# define sentry__rwlock_init(Lock) InitializeSRWLock(Lock) +# define sentry__rwlock_read_lock(Lock) AcquireSRWLockShared(Lock) +# define sentry__rwlock_read_unlock(Lock) ReleaseSRWLockShared(Lock) +# define sentry__rwlock_write_lock(Lock) AcquireSRWLockExclusive(Lock) +# define sentry__rwlock_write_unlock(Lock) ReleaseSRWLockExclusive(Lock) +# define sentry__rwlock_free(Lock) ((void)0) +# else +typedef struct sentry__winrwlock_s { + INIT_ONCE init_once; + CRITICAL_SECTION lock; + CONDITION_VARIABLE_PREVISTA readers_cond; + CONDITION_VARIABLE_PREVISTA writers_cond; + unsigned readers; + unsigned writers_waiting; + bool writer; +} sentry_rwlock_t; + +# define SENTRY__RWLOCK_INIT \ + { INIT_ONCE_STATIC_INIT, { 0 }, { 0 }, { 0 }, 0, 0, false } + +static inline BOOL CALLBACK +sentry__winrwlock_initonce( + PINIT_ONCE UNUSED(InitOnce), PVOID data, PVOID *UNUSED(lpContext)) +{ + sentry_rwlock_t *rwlock = (sentry_rwlock_t *)data; + InitializeCriticalSection(&rwlock->lock); + InitializeConditionVariable_PREVISTA(&rwlock->readers_cond); + InitializeConditionVariable_PREVISTA(&rwlock->writers_cond); + rwlock->readers = 0; + rwlock->writers_waiting = 0; + rwlock->writer = false; + return TRUE; +} + +static inline void +sentry__winrwlock_ensure_init(sentry_rwlock_t *rwlock) +{ + InitOnceExecuteOnce( + &rwlock->init_once, sentry__winrwlock_initonce, rwlock, NULL); +} + +static inline void +sentry__winrwlock_init(sentry_rwlock_t *rwlock) +{ + sentry_rwlock_t tmp = SENTRY__RWLOCK_INIT; + *rwlock = tmp; + sentry__winrwlock_ensure_init(rwlock); +} + +static inline void +sentry__winrwlock_read_lock(sentry_rwlock_t *rwlock) +{ + sentry__winrwlock_ensure_init(rwlock); + EnterCriticalSection(&rwlock->lock); + while (rwlock->writer || rwlock->writers_waiting > 0) { + SleepConditionVariableCS_PREVISTA( + &rwlock->readers_cond, &rwlock->lock, INFINITE); + } + rwlock->readers++; + if (rwlock->writers_waiting == 0) { + WakeConditionVariable_PREVISTA(&rwlock->readers_cond); + } + LeaveCriticalSection(&rwlock->lock); +} + +static inline void +sentry__winrwlock_read_unlock(sentry_rwlock_t *rwlock) +{ + EnterCriticalSection(&rwlock->lock); + assert(rwlock->readers > 0); + rwlock->readers--; + if (rwlock->readers == 0 && rwlock->writers_waiting > 0) { + WakeConditionVariable_PREVISTA(&rwlock->writers_cond); + } + LeaveCriticalSection(&rwlock->lock); +} + +static inline void +sentry__winrwlock_write_lock(sentry_rwlock_t *rwlock) +{ + sentry__winrwlock_ensure_init(rwlock); + EnterCriticalSection(&rwlock->lock); + rwlock->writers_waiting++; + while (rwlock->writer || rwlock->readers > 0) { + SleepConditionVariableCS_PREVISTA( + &rwlock->writers_cond, &rwlock->lock, INFINITE); + } + rwlock->writers_waiting--; + rwlock->writer = true; + LeaveCriticalSection(&rwlock->lock); +} + +static inline void +sentry__winrwlock_write_unlock(sentry_rwlock_t *rwlock) +{ + EnterCriticalSection(&rwlock->lock); + assert(rwlock->writer); + rwlock->writer = false; + if (rwlock->writers_waiting > 0) { + WakeConditionVariable_PREVISTA(&rwlock->writers_cond); + } else { + WakeConditionVariable_PREVISTA(&rwlock->readers_cond); + } + LeaveCriticalSection(&rwlock->lock); +} + +static inline void +sentry__winrwlock_free(sentry_rwlock_t *rwlock) +{ + sentry__winrwlock_ensure_init(rwlock); + DeleteCriticalSection(&rwlock->lock); + CloseHandle(rwlock->readers_cond.Semaphore); + CloseHandle(rwlock->readers_cond.ContinueEvent); + CloseHandle(rwlock->writers_cond.Semaphore); + CloseHandle(rwlock->writers_cond.ContinueEvent); +} + +# define sentry__rwlock_init(Lock) sentry__winrwlock_init(Lock) +# define sentry__rwlock_read_lock(Lock) sentry__winrwlock_read_lock(Lock) +# define sentry__rwlock_read_unlock(Lock) \ + sentry__winrwlock_read_unlock(Lock) +# define sentry__rwlock_write_lock(Lock) \ + sentry__winrwlock_write_lock(Lock) +# define sentry__rwlock_write_unlock(Lock) \ + sentry__winrwlock_write_unlock(Lock) +# define sentry__rwlock_free(Lock) sentry__winrwlock_free(Lock) +# endif + # define sentry__thread_init(ThreadId) *ThreadId = INVALID_HANDLE_VALUE # define sentry__thread_spawn(ThreadId, Func, Data) \ (*ThreadId = CreateThread(NULL, 0, Func, Data, 0, NULL), \ @@ -268,6 +400,7 @@ void sentry__leave_signal_handler(void); typedef pthread_t sentry_threadid_t; typedef pthread_mutex_t sentry_mutex_t; +typedef pthread_rwlock_t sentry_rwlock_t; typedef pthread_cond_t sentry_cond_t; # ifdef SENTRY_PLATFORM_LINUX @@ -349,6 +482,57 @@ typedef pthread_cond_t sentry_cond_t; } \ } while (0) # define sentry__mutex_free(Lock) pthread_mutex_destroy(Lock) +# ifdef PTHREAD_RWLOCK_INITIALIZER +# define SENTRY__RWLOCK_INIT PTHREAD_RWLOCK_INITIALIZER +# define sentry__rwlock_init(Rwlock) \ + do { \ + sentry_rwlock_t tmp = SENTRY__RWLOCK_INIT; \ + *(Rwlock) = tmp; \ + } while (0) +# else +# define SENTRY__RWLOCK_INIT_DYN(Rwlock) \ + static sentry_rwlock_t Rwlock; \ + static pthread_once_t Rwlock##_init_once = PTHREAD_ONCE_INIT; \ + static void init_##Rwlock(void) { sentry__rwlock_init(&Rwlock); } +# undef SENTRY__RWLOCK_INIT_DYN_ONCE +# define SENTRY__RWLOCK_INIT_DYN_ONCE(Rwlock) \ + pthread_once(&Rwlock##_init_once, init_##Rwlock) +# define sentry__rwlock_init(Rwlock) \ + do { \ + int rv = pthread_rwlock_init(Rwlock, NULL); \ + (void)rv; \ + assert(rv == 0); \ + } while (0) +# endif +# define sentry__rwlock_read_lock(Rwlock) \ + do { \ + if (sentry__block_for_signal_handler()) { \ + int rv = pthread_rwlock_rdlock(Rwlock); \ + (void)rv; \ + assert(rv == 0); \ + } \ + } while (0) +# define sentry__rwlock_read_unlock(Rwlock) \ + do { \ + if (sentry__block_for_signal_handler()) { \ + pthread_rwlock_unlock(Rwlock); \ + } \ + } while (0) +# define sentry__rwlock_write_lock(Rwlock) \ + do { \ + if (sentry__block_for_signal_handler()) { \ + int rv = pthread_rwlock_wrlock(Rwlock); \ + (void)rv; \ + assert(rv == 0); \ + } \ + } while (0) +# define sentry__rwlock_write_unlock(Rwlock) \ + do { \ + if (sentry__block_for_signal_handler()) { \ + pthread_rwlock_unlock(Rwlock); \ + } \ + } while (0) +# define sentry__rwlock_free(Rwlock) pthread_rwlock_destroy(Rwlock) # define sentry__cond_init(CondVar) \ do { \ diff --git a/tests/unit/test_sync.c b/tests/unit/test_sync.c index d73df51064..38ec25a9e1 100644 --- a/tests/unit/test_sync.c +++ b/tests/unit/test_sync.c @@ -5,6 +5,276 @@ #include "sentry_utils.h" #include +#define RWLOCK_WAIT_TIMEOUT_MS 2000 + +static bool +wait_for_atomic_value(volatile long *value, long expected) +{ + uint64_t deadline = sentry__monotonic_time() + RWLOCK_WAIT_TIMEOUT_MS; + while (sentry__atomic_fetch(value) != expected) { + if (sentry__monotonic_time() >= deadline) { + return false; + } + sentry__thread_yield(); + sleep_ms(1); + } + return true; +} + +#ifdef SENTRY__RWLOCK_INIT_DYN +SENTRY__RWLOCK_INIT_DYN(static_rwlock) +#else +static sentry_rwlock_t static_rwlock = SENTRY__RWLOCK_INIT; +#endif + +SENTRY_TEST(rwlock_static_init) +{ + SENTRY__RWLOCK_INIT_DYN_ONCE(static_rwlock); + + sentry__rwlock_read_lock(&static_rwlock); + sentry__rwlock_read_unlock(&static_rwlock); + + sentry__rwlock_write_lock(&static_rwlock); + sentry__rwlock_write_unlock(&static_rwlock); +} + +SENTRY_TEST(rwlock_dynamic_init) +{ + sentry_rwlock_t rwlock; + sentry__rwlock_init(&rwlock); + + sentry__rwlock_read_lock(&rwlock); + sentry__rwlock_read_unlock(&rwlock); + + sentry__rwlock_write_lock(&rwlock); + sentry__rwlock_write_unlock(&rwlock); + + sentry__rwlock_free(&rwlock); +} + +typedef struct { + sentry_rwlock_t *rwlock; + volatile long start; + volatile long release; + volatile long entered; + volatile long active_readers; +} rwlock_readers_state_t; + +SENTRY_THREAD_FN +rwlock_reader_thread(void *data) +{ + rwlock_readers_state_t *state = (rwlock_readers_state_t *)data; + while (!sentry__atomic_fetch(&state->start)) { + sentry__thread_yield(); + } + + sentry__rwlock_read_lock(state->rwlock); + sentry__atomic_fetch_and_add(&state->active_readers, 1); + sentry__atomic_fetch_and_add(&state->entered, 1); + while (!sentry__atomic_fetch(&state->release)) { + sentry__thread_yield(); + } + sentry__atomic_fetch_and_add(&state->active_readers, -1); + sentry__rwlock_read_unlock(state->rwlock); + return 0; +} + +SENTRY_TEST(rwlock_shared_readers) +{ + enum { NUM_READERS = 8 }; + + sentry_rwlock_t rwlock; + sentry__rwlock_init(&rwlock); + + rwlock_readers_state_t state = { &rwlock, 0, 0, 0, 0 }; + sentry_threadid_t threads[NUM_READERS]; + for (int i = 0; i < NUM_READERS; i++) { + sentry__thread_init(&threads[i]); + TEST_CHECK_INT_EQUAL( + sentry__thread_spawn(&threads[i], rwlock_reader_thread, &state), 0); + } + + sentry__atomic_store(&state.start, 1); + TEST_CHECK(wait_for_atomic_value(&state.entered, NUM_READERS)); + TEST_CHECK_INT_EQUAL( + sentry__atomic_fetch(&state.active_readers), NUM_READERS); + + sentry__atomic_store(&state.release, 1); + for (int i = 0; i < NUM_READERS; i++) { + sentry__thread_join(threads[i]); + } + + sentry__rwlock_free(&rwlock); +} + +typedef struct { + sentry_rwlock_t *rwlock; + bool write; + volatile long attempting; + volatile long entered; + volatile long release; +} rwlock_waiter_state_t; + +SENTRY_THREAD_FN +rwlock_waiter_thread(void *data) +{ + rwlock_waiter_state_t *state = (rwlock_waiter_state_t *)data; + sentry__atomic_store(&state->attempting, 1); + if (state->write) { + sentry__rwlock_write_lock(state->rwlock); + } else { + sentry__rwlock_read_lock(state->rwlock); + } + sentry__atomic_store(&state->entered, 1); + while (!sentry__atomic_fetch(&state->release)) { + sentry__thread_yield(); + } + if (state->write) { + sentry__rwlock_write_unlock(state->rwlock); + } else { + sentry__rwlock_read_unlock(state->rwlock); + } + return 0; +} + +static void +check_rwlock_blocks_waiter(bool holder_write, bool waiter_write) +{ + sentry_rwlock_t rwlock; + sentry__rwlock_init(&rwlock); + + if (holder_write) { + sentry__rwlock_write_lock(&rwlock); + } else { + sentry__rwlock_read_lock(&rwlock); + } + + rwlock_waiter_state_t state = { &rwlock, waiter_write, 0, 0, 0 }; + sentry_threadid_t thread; + sentry__thread_init(&thread); + TEST_CHECK_INT_EQUAL( + sentry__thread_spawn(&thread, rwlock_waiter_thread, &state), 0); + + TEST_CHECK(wait_for_atomic_value(&state.attempting, 1)); + sleep_ms(100); + TEST_CHECK_INT_EQUAL(sentry__atomic_fetch(&state.entered), 0); + + if (holder_write) { + sentry__rwlock_write_unlock(&rwlock); + } else { + sentry__rwlock_read_unlock(&rwlock); + } + + TEST_CHECK(wait_for_atomic_value(&state.entered, 1)); + sentry__atomic_store(&state.release, 1); + sentry__thread_join(thread); + + sentry__rwlock_free(&rwlock); +} + +SENTRY_TEST(rwlock_read_blocks_writer) +{ + check_rwlock_blocks_waiter(false, true); +} + +SENTRY_TEST(rwlock_write_blocks_reader) +{ + check_rwlock_blocks_waiter(true, false); +} + +SENTRY_TEST(rwlock_write_blocks_writer) +{ + check_rwlock_blocks_waiter(true, true); +} + +typedef struct { + sentry_rwlock_t *rwlock; + volatile long start; + volatile long writers_done; + volatile long failed; + long writers_total; + long counter; +} rwlock_stress_state_t; + +SENTRY_THREAD_FN +rwlock_writer_thread(void *data) +{ + rwlock_stress_state_t *state = (rwlock_stress_state_t *)data; + while (!sentry__atomic_fetch(&state->start)) { + sentry__thread_yield(); + } + + for (int i = 0; i < 1000; i++) { + sentry__rwlock_write_lock(state->rwlock); + state->counter++; + sentry__rwlock_write_unlock(state->rwlock); + if (i % 16 == 0) { + sentry__thread_yield(); + } + } + sentry__atomic_fetch_and_add(&state->writers_done, 1); + return 0; +} + +SENTRY_THREAD_FN +rwlock_stress_reader_thread(void *data) +{ + rwlock_stress_state_t *state = (rwlock_stress_state_t *)data; + long previous = 0; + while (!sentry__atomic_fetch(&state->start)) { + sentry__thread_yield(); + } + + while (sentry__atomic_fetch(&state->writers_done) < state->writers_total) { + sentry__rwlock_read_lock(state->rwlock); + long current = state->counter; + sentry__rwlock_read_unlock(state->rwlock); + if (current < previous) { + sentry__atomic_store(&state->failed, 1); + } + previous = current; + sentry__thread_yield(); + } + return 0; +} + +SENTRY_TEST(rwlock_stress) +{ + enum { NUM_WRITERS = 4, NUM_READERS = 4 }; + + sentry_rwlock_t rwlock; + sentry__rwlock_init(&rwlock); + + rwlock_stress_state_t state = { &rwlock, 0, 0, 0, NUM_WRITERS, 0 }; + sentry_threadid_t writers[NUM_WRITERS]; + sentry_threadid_t readers[NUM_READERS]; + + for (int i = 0; i < NUM_WRITERS; i++) { + sentry__thread_init(&writers[i]); + TEST_CHECK_INT_EQUAL( + sentry__thread_spawn(&writers[i], rwlock_writer_thread, &state), 0); + } + for (int i = 0; i < NUM_READERS; i++) { + sentry__thread_init(&readers[i]); + TEST_CHECK_INT_EQUAL(sentry__thread_spawn(&readers[i], + rwlock_stress_reader_thread, &state), + 0); + } + + sentry__atomic_store(&state.start, 1); + for (int i = 0; i < NUM_WRITERS; i++) { + sentry__thread_join(writers[i]); + } + for (int i = 0; i < NUM_READERS; i++) { + sentry__thread_join(readers[i]); + } + + TEST_CHECK_INT_EQUAL(state.counter, NUM_WRITERS * 1000); + TEST_CHECK_INT_EQUAL(sentry__atomic_fetch(&state.failed), 0); + + sentry__rwlock_free(&rwlock); +} + struct task_state { int executed; bool running; diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 201c435043..391063f970 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -306,6 +306,13 @@ XX(ringbuffer_max_size_null_noop) XX(ringbuffer_max_size_post_init) XX(ringbuffer_to_list_null_value_null) XX(ringbuffer_zero_noop) +XX(rwlock_dynamic_init) +XX(rwlock_read_blocks_writer) +XX(rwlock_shared_readers) +XX(rwlock_static_init) +XX(rwlock_stress) +XX(rwlock_write_blocks_reader) +XX(rwlock_write_blocks_writer) XX(sampling_before_send) XX(sampling_decision) XX(sampling_transaction) From 4d4309d061c7656dae662e9f66adf1b4a47bae7e Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 5 Aug 2026 17:58:03 +0200 Subject: [PATCH 04/22] refactor(sync): make rwlock operations inline Convert the POSIX rwlock operations from statement macros to static inline functions so callers can use them from scoped lock macro expressions. --- src/sentry_sync.h | 97 ++++++++++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 40 deletions(-) diff --git a/src/sentry_sync.h b/src/sentry_sync.h index 39cfbe81f5..61a72edfb2 100644 --- a/src/sentry_sync.h +++ b/src/sentry_sync.h @@ -484,12 +484,22 @@ typedef pthread_cond_t sentry_cond_t; # define sentry__mutex_free(Lock) pthread_mutex_destroy(Lock) # ifdef PTHREAD_RWLOCK_INITIALIZER # define SENTRY__RWLOCK_INIT PTHREAD_RWLOCK_INITIALIZER -# define sentry__rwlock_init(Rwlock) \ - do { \ - sentry_rwlock_t tmp = SENTRY__RWLOCK_INIT; \ - *(Rwlock) = tmp; \ - } while (0) +# endif + +static inline void +sentry__rwlock_init(sentry_rwlock_t *rwlock) +{ +# ifdef PTHREAD_RWLOCK_INITIALIZER + sentry_rwlock_t tmp = SENTRY__RWLOCK_INIT; + *rwlock = tmp; # else + int rv = pthread_rwlock_init(rwlock, NULL); + (void)rv; + assert(rv == 0); +# endif +} + +# ifndef PTHREAD_RWLOCK_INITIALIZER # define SENTRY__RWLOCK_INIT_DYN(Rwlock) \ static sentry_rwlock_t Rwlock; \ static pthread_once_t Rwlock##_init_once = PTHREAD_ONCE_INIT; \ @@ -497,42 +507,49 @@ typedef pthread_cond_t sentry_cond_t; # undef SENTRY__RWLOCK_INIT_DYN_ONCE # define SENTRY__RWLOCK_INIT_DYN_ONCE(Rwlock) \ pthread_once(&Rwlock##_init_once, init_##Rwlock) -# define sentry__rwlock_init(Rwlock) \ - do { \ - int rv = pthread_rwlock_init(Rwlock, NULL); \ - (void)rv; \ - assert(rv == 0); \ - } while (0) # endif -# define sentry__rwlock_read_lock(Rwlock) \ - do { \ - if (sentry__block_for_signal_handler()) { \ - int rv = pthread_rwlock_rdlock(Rwlock); \ - (void)rv; \ - assert(rv == 0); \ - } \ - } while (0) -# define sentry__rwlock_read_unlock(Rwlock) \ - do { \ - if (sentry__block_for_signal_handler()) { \ - pthread_rwlock_unlock(Rwlock); \ - } \ - } while (0) -# define sentry__rwlock_write_lock(Rwlock) \ - do { \ - if (sentry__block_for_signal_handler()) { \ - int rv = pthread_rwlock_wrlock(Rwlock); \ - (void)rv; \ - assert(rv == 0); \ - } \ - } while (0) -# define sentry__rwlock_write_unlock(Rwlock) \ - do { \ - if (sentry__block_for_signal_handler()) { \ - pthread_rwlock_unlock(Rwlock); \ - } \ - } while (0) -# define sentry__rwlock_free(Rwlock) pthread_rwlock_destroy(Rwlock) + +static inline void +sentry__rwlock_read_lock(sentry_rwlock_t *rwlock) +{ + if (sentry__block_for_signal_handler()) { + int rv = pthread_rwlock_rdlock(rwlock); + (void)rv; + assert(rv == 0); + } +} + +static inline void +sentry__rwlock_read_unlock(sentry_rwlock_t *rwlock) +{ + if (sentry__block_for_signal_handler()) { + pthread_rwlock_unlock(rwlock); + } +} + +static inline void +sentry__rwlock_write_lock(sentry_rwlock_t *rwlock) +{ + if (sentry__block_for_signal_handler()) { + int rv = pthread_rwlock_wrlock(rwlock); + (void)rv; + assert(rv == 0); + } +} + +static inline void +sentry__rwlock_write_unlock(sentry_rwlock_t *rwlock) +{ + if (sentry__block_for_signal_handler()) { + pthread_rwlock_unlock(rwlock); + } +} + +static inline void +sentry__rwlock_free(sentry_rwlock_t *rwlock) +{ + pthread_rwlock_destroy(rwlock); +} # define sentry__cond_init(CondVar) \ do { \ From f765fbc32c7341b8975ec8d547fc0c48a0d24a87 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 5 Aug 2026 18:05:48 +0200 Subject: [PATCH 05/22] refactor(scope): guard simple fields with rwlock Add a per-scope rwlock and use scoped read/write locking for simple by-value fields. Keep borrowed scope properties unchanged for now. --- src/sentry_core.c | 2 +- src/sentry_scope.c | 89 +++++++++++++++++++++++++++++++---------- src/sentry_scope.h | 10 ++++- tests/unit/test_scope.c | 2 +- 4 files changed, 78 insertions(+), 25 deletions(-) diff --git a/src/sentry_core.c b/src/sentry_core.c index 5ce4310d6d..5dd5822547 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -471,7 +471,7 @@ sentry__capture_envelope(sentry_transport_t *transport, sentry_uuid_t event_id = sentry__envelope_get_event_id(envelope); if (!sentry_uuid_is_nil(&event_id)) { SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) { - scope->last_event_id = event_id; + sentry__scope_set_last_event_id(scope, event_id); } } diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 008b4a10ba..f956461959 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -36,6 +36,24 @@ SENTRY__MUTEX_INIT_DYN(g_lock) static sentry_mutex_t g_lock = SENTRY__MUTEX_INIT; #endif +#define SCOPE_READ_LOCK(Scope) \ + for (const sentry_scope_t *_locked_scope = (Scope); _locked_scope; \ + sentry__rwlock_read_unlock((sentry_rwlock_t *)&_locked_scope->rwlock), \ + _locked_scope = NULL) \ + for (bool _locked_once \ + = (sentry__rwlock_read_lock( \ + (sentry_rwlock_t *)&_locked_scope->rwlock), \ + true); \ + _locked_once; _locked_once = false) + +#define SCOPE_WRITE_LOCK(Scope) \ + for (sentry_scope_t *_locked_scope = (Scope); _locked_scope; \ + sentry__rwlock_write_unlock(&_locked_scope->rwlock), \ + _locked_scope = NULL) \ + for (bool _locked_once \ + = (sentry__rwlock_write_lock(&_locked_scope->rwlock), true); \ + _locked_once; _locked_once = false) + static sentry_value_t get_client_sdk(void) { @@ -124,6 +142,7 @@ get_scope(void) } memset(&g_scope, 0, sizeof(sentry_scope_t)); + sentry__rwlock_init(&g_scope.rwlock); init_scope(&g_scope); g_scope.user = sentry_value_new_object(); sentry_value_set_by_key(g_scope.contexts, "os", sentry__get_os_context()); @@ -170,6 +189,7 @@ sentry__scope_cleanup(void) if (g_scope_initialized) { g_scope_initialized = false; cleanup_scope(&g_scope); + sentry__rwlock_free(&g_scope.rwlock); } sentry__mutex_unlock(&g_lock); } @@ -324,6 +344,7 @@ sentry_scope_new(void) return NULL; } + sentry__rwlock_init(&scope->rwlock); init_scope(scope); return scope; } @@ -336,13 +357,28 @@ sentry_scope_free(sentry_scope_t *scope) } cleanup_scope(scope); + sentry__rwlock_free(&scope->rwlock); sentry_free(scope); } +bool +sentry__scope_is_one_shot(const sentry_scope_t *scope) +{ + bool one_shot = false; + SCOPE_READ_LOCK(scope) { one_shot = scope->one_shot; } + return one_shot; +} + +void +sentry__scope_set_one_shot(sentry_scope_t *scope, bool one_shot) +{ + SCOPE_WRITE_LOCK(scope) { scope->one_shot = one_shot; } +} + void sentry__scope_free_one_shot(sentry_scope_t *scope) { - if (scope && scope->one_shot) { + if (sentry__scope_is_one_shot(scope)) { sentry_scope_free(scope); } } @@ -352,7 +388,7 @@ sentry_local_scope_new(void) { sentry_scope_t *scope = sentry_scope_new(); if (scope) { - scope->one_shot = true; + sentry__scope_set_one_shot(scope, true); } return scope; } @@ -404,12 +440,12 @@ sentry_scope_clear(sentry_scope_t *scope) // Keep the propagation and dynamic sampling contexts across clears so // telemetry captured afterwards continues on the same trace. - bool trace_managed = scope->trace_managed; + bool trace_managed = sentry__scope_is_trace_managed(scope); sentry_value_t propagation_context = scope->propagation_context; sentry_value_t dynamic_sampling_context = scope->dynamic_sampling_context; sentry_value_incref(propagation_context); sentry_value_incref(dynamic_sampling_context); - bool one_shot = scope->one_shot; + bool one_shot = sentry__scope_is_one_shot(scope); cleanup_scope(scope); init_scope(scope); @@ -418,8 +454,8 @@ sentry_scope_clear(sentry_scope_t *scope) sentry_value_decref(scope->dynamic_sampling_context); scope->propagation_context = propagation_context; scope->dynamic_sampling_context = dynamic_sampling_context; - scope->trace_managed = trace_managed; - scope->one_shot = one_shot; + sentry__scope_set_trace_managed(scope, trace_managed); + sentry__scope_set_one_shot(scope, one_shot); scope->observers = observers; scope->num_observers = num_observers; scope->is_notifying = is_notifying; @@ -438,6 +474,8 @@ sentry_scope_clone(const sentry_scope_t *scope) return NULL; } + sentry__rwlock_init(&clone->rwlock); + clone->release = sentry__string_clone(scope->release); clone->environment = sentry__string_clone(scope->environment); clone->transaction = sentry__string_clone(scope->transaction); @@ -455,8 +493,8 @@ sentry_scope_clone(const sentry_scope_t *scope) if (sentry_value_is_frozen(scope->dynamic_sampling_context)) { sentry_value_freeze(clone->dynamic_sampling_context); } - clone->level = scope->level; - clone->last_event_id = scope->last_event_id; + clone->level = sentry__scope_get_level(scope); + clone->last_event_id = sentry_scope_get_last_event_id(scope); clone->client_sdk = sentry__value_clone(scope->client_sdk); clone->attachments = sentry__attachments_new(); sentry__attachments_extend(&clone->attachments, scope->attachments); @@ -465,7 +503,7 @@ sentry_scope_clone(const sentry_scope_t *scope) sentry__transaction_incref(clone->transaction_object); clone->span = scope->span; sentry__span_incref(clone->span); - clone->trace_managed = scope->trace_managed; + clone->trace_managed = sentry__scope_is_trace_managed(scope); return clone; } @@ -498,13 +536,15 @@ sentry__scope_regenerate_propagation_context(sentry_scope_t *scope) bool sentry__scope_is_trace_managed(const sentry_scope_t *scope) { - return scope->trace_managed; + bool managed = false; + SCOPE_READ_LOCK(scope) { managed = scope->trace_managed; } + return managed; } void sentry__scope_set_trace_managed(sentry_scope_t *scope, bool managed) { - scope->trace_managed = managed; + SCOPE_WRITE_LOCK(scope) { scope->trace_managed = managed; } } sentry_value_t @@ -687,12 +727,6 @@ sentry__scope_has_observers(const sentry_scope_t *scope) { return scope->num_observers > 0; } - -bool -sentry__scope_is_one_shot(const sentry_scope_t *scope) -{ - return scope->one_shot; -} #endif void @@ -730,7 +764,7 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, // is not transaction and has no level if (IS_NULL("type") && IS_NULL("level")) { - SET("level", sentry__value_new_level(scope->level)); + SET("level", sentry__value_new_level(sentry__scope_get_level(scope))); } if (sentry_value_get_type(scope->user) == SENTRY_VALUE_TYPE_OBJECT) { @@ -1186,13 +1220,15 @@ sentry_scope_remove_fingerprint(sentry_scope_t *scope) sentry_level_t sentry__scope_get_level(const sentry_scope_t *scope) { - return scope->level; + sentry_level_t level = SENTRY_LEVEL_ERROR; + SCOPE_READ_LOCK(scope) { level = scope->level; } + return level; } void sentry_scope_set_level(sentry_scope_t *scope, sentry_level_t level) { - scope->level = level; + SCOPE_WRITE_LOCK(scope) { scope->level = level; } SENTRY_SCOPE_NOTIFY(scope, set_level, level); } @@ -1584,7 +1620,16 @@ sentry__scope_apply_to_telemetry(const sentry_scope_t *scope, sentry_uuid_t sentry_scope_get_last_event_id(const sentry_scope_t *scope) { - return scope ? scope->last_event_id : sentry_uuid_nil(); + sentry_uuid_t event_id = sentry_uuid_nil(); + SCOPE_READ_LOCK(scope) { event_id = scope->last_event_id; } + return event_id; +} + +void +sentry__scope_set_last_event_id( + sentry_scope_t *scope, sentry_uuid_t event_id) +{ + SCOPE_WRITE_LOCK(scope) { scope->last_event_id = event_id; } } void @@ -1594,7 +1639,7 @@ sentry__scope_capture_envelope(sentry_scope_t *scope, { sentry_uuid_t event_id = sentry__envelope_get_event_id(envelope); if (!sentry_uuid_is_nil(&event_id)) { - scope->last_event_id = event_id; + sentry__scope_set_last_event_id(scope, event_id); } sentry__submit_envelope(transport, envelope, options); diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 4bc100ffc9..ef8e4aa95b 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -6,6 +6,7 @@ #include "sentry_attachment.h" #include "sentry_ringbuffer.h" #include "sentry_session.h" +#include "sentry_sync.h" #include "sentry_value.h" /** @@ -53,6 +54,8 @@ typedef struct sentry_scope_observer_s { * This represents the current scope. */ struct sentry_scope_s { + sentry_rwlock_t rwlock; + char *release; char *environment; char *transaction; @@ -126,6 +129,9 @@ void sentry__scope_cleanup(void); void sentry__scope_apply_options( sentry_scope_t *scope, sentry_options_t *options); +bool sentry__scope_is_one_shot(const sentry_scope_t *scope); +void sentry__scope_set_one_shot(sentry_scope_t *scope, bool one_shot); + /** * Frees the scope if it is a one-shot local scope. */ @@ -301,6 +307,9 @@ void sentry__scope_freeze_dsc(sentry_scope_t *scope, sentry_value_t incoming); void sentry__scope_apply_to_telemetry(const sentry_scope_t *scope, sentry_value_t telemetry, sentry_value_t attributes); +void sentry__scope_set_last_event_id( + sentry_scope_t *scope, sentry_uuid_t event_id); + /** * Captures the `envelope` on `scope`, recording the last sent event ID. */ @@ -314,5 +323,4 @@ void sentry__scope_capture_envelope(sentry_scope_t *scope, #ifdef SENTRY_UNITTEST sentry_value_t sentry__scope_get_span_or_transaction(void); bool sentry__scope_has_observers(const sentry_scope_t *scope); -bool sentry__scope_is_one_shot(const sentry_scope_t *scope); #endif diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 1723cfa0e9..f84e4f253d 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -2458,7 +2458,7 @@ SENTRY_TEST(scope_clone_independence) sentry_scope_t *clone = sentry_scope_clone(scope); // A clone is reusable, never one-shot. - TEST_CHECK(!clone->one_shot); + TEST_CHECK(!sentry__scope_is_one_shot(clone)); // The clone carries over the source's data. TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( From a755c910723258146ea967541693e6d9a138a185 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 5 Aug 2026 18:39:32 +0200 Subject: [PATCH 06/22] refactor(scope): retain user and fingerprint values safely Add ref-style scope accessors for user and fingerprint values so callers get a retained reference that was incremented under the scope read lock. Guard user and fingerprint replacement with the scope write lock and keep observer notifications outside the locked section. --- src/sentry_scope.c | 108 ++++++++++++++++++++++++++++------------ src/sentry_scope.h | 4 +- src/sentry_session.c | 5 +- tests/unit/test_scope.c | 58 ++++++++++++++++----- 4 files changed, 127 insertions(+), 48 deletions(-) diff --git a/src/sentry_scope.c b/src/sentry_scope.c index f956461959..7acf6b1530 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -479,8 +479,13 @@ sentry_scope_clone(const sentry_scope_t *scope) clone->release = sentry__string_clone(scope->release); clone->environment = sentry__string_clone(scope->environment); clone->transaction = sentry__string_clone(scope->transaction); - clone->fingerprint = sentry__value_clone(scope->fingerprint); - clone->user = sentry__value_clone(scope->user); + sentry_value_t fingerprint = sentry__scope_ref_fingerprint(scope); + clone->fingerprint = sentry__value_clone(fingerprint); + sentry_value_decref(fingerprint); + + sentry_value_t user = sentry__scope_ref_user(scope); + clone->user = sentry__value_clone(user); + sentry_value_decref(user); clone->tags = sentry__value_clone(scope->tags); clone->extra = sentry__value_clone(scope->extra); clone->attributes = sentry__value_clone(scope->attributes); @@ -767,24 +772,31 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, SET("level", sentry__value_new_level(sentry__scope_get_level(scope))); } - if (sentry_value_get_type(scope->user) == SENTRY_VALUE_TYPE_OBJECT) { + sentry_value_t user = sentry__scope_ref_user(scope); + if (sentry_value_get_type(user) == SENTRY_VALUE_TYPE_OBJECT) { if (options->run && options->run->installation_id) { // ensure event has a user object if (IS_NULL("user")) { - SET("user", sentry__value_clone(scope->user)); + SET("user", sentry__value_clone(user)); } // patch missing user ID with installation ID - sentry_value_t user = sentry_value_get_by_key(event, "user"); - if (sentry_value_get_type(user) == SENTRY_VALUE_TYPE_OBJECT - && sentry_value_is_null(sentry_value_get_by_key(user, "id"))) { - sentry_value_set_by_key(user, "id", + sentry_value_t event_user = sentry_value_get_by_key(event, "user"); + if (sentry_value_get_type(event_user) == SENTRY_VALUE_TYPE_OBJECT + && sentry_value_is_null( + sentry_value_get_by_key(event_user, "id"))) { + sentry_value_set_by_key(event_user, "id", sentry_value_new_string(options->run->installation_id)); } - } else if (sentry_value_get_length(scope->user) > 0) { - PLACE_CLONED_VALUE("user", scope->user); + } else if (sentry_value_get_length(user) > 0) { + PLACE_CLONED_VALUE("user", user); } } - PLACE_CLONED_VALUE("fingerprint", scope->fingerprint); + sentry_value_decref(user); + + sentry_value_t fingerprint = sentry__scope_ref_fingerprint(scope); + PLACE_CLONED_VALUE("fingerprint", fingerprint); + sentry_value_decref(fingerprint); + PLACE_STRING("transaction", scope->transaction); PLACE_VALUE("sdk", scope->client_sdk); @@ -896,17 +908,27 @@ sentry__scope_get_breadcrumbs(const sentry_scope_t *scope) } sentry_value_t -sentry__scope_get_user(const sentry_scope_t *scope) +sentry__scope_ref_user(const sentry_scope_t *scope) { - return scope->user; + sentry_value_t user = sentry_value_new_null(); + SCOPE_READ_LOCK(scope) { user = sentry_value_incref(scope->user); } + return user; } void sentry_scope_set_user(sentry_scope_t *scope, sentry_value_t user) { - sentry_value_decref(scope->user); - scope->user = user; - SENTRY_SCOPE_NOTIFY(scope, set_user, user); + sentry_value_t old_user = sentry_value_new_null(); + sentry_value_t notify_user = sentry_value_new_null(); + SCOPE_WRITE_LOCK(scope) + { + old_user = scope->user; + scope->user = user; + notify_user = sentry_value_incref(user); + } + sentry_value_decref(old_user); + SENTRY_SCOPE_NOTIFY(scope, set_user, notify_user); + sentry_value_decref(notify_user); } sentry_value_t @@ -1134,9 +1156,14 @@ sentry__scope_remove_context_n( } sentry_value_t -sentry__scope_get_fingerprint(const sentry_scope_t *scope) +sentry__scope_ref_fingerprint(const sentry_scope_t *scope) { - return scope->fingerprint; + sentry_value_t fingerprint = sentry_value_new_null(); + SCOPE_READ_LOCK(scope) + { + fingerprint = sentry_value_incref(scope->fingerprint); + } + return fingerprint; } void @@ -1149,9 +1176,7 @@ sentry__scope_set_fingerprint_va( fingerprint_value, sentry_value_new_string(fingerprint)); } - sentry_value_decref(scope->fingerprint); - scope->fingerprint = fingerprint_value; - SENTRY_SCOPE_NOTIFY(scope, set_fingerprint, fingerprint_value); + sentry_scope_set_fingerprints(scope, fingerprint_value); } void @@ -1204,17 +1229,34 @@ sentry_scope_set_fingerprints( return; } - sentry_value_decref(scope->fingerprint); - scope->fingerprint = fingerprints; - SENTRY_SCOPE_NOTIFY(scope, set_fingerprint, fingerprints); + sentry_value_t old_fingerprint = sentry_value_new_null(); + sentry_value_t notify_fingerprint = sentry_value_new_null(); + SCOPE_WRITE_LOCK(scope) + { + old_fingerprint = scope->fingerprint; + scope->fingerprint = fingerprints; + notify_fingerprint = sentry_value_incref(fingerprints); + } + sentry_value_decref(old_fingerprint); + SENTRY_SCOPE_NOTIFY(scope, set_fingerprint, notify_fingerprint); + sentry_value_decref(notify_fingerprint); } void sentry_scope_remove_fingerprint(sentry_scope_t *scope) { - sentry_value_decref(scope->fingerprint); - scope->fingerprint = sentry_value_new_null(); - SENTRY_SCOPE_NOTIFY(scope, set_fingerprint, scope->fingerprint); + sentry_value_t fingerprint = sentry_value_new_null(); + sentry_value_t old_fingerprint = sentry_value_new_null(); + sentry_value_t notify_fingerprint = sentry_value_new_null(); + SCOPE_WRITE_LOCK(scope) + { + old_fingerprint = scope->fingerprint; + scope->fingerprint = fingerprint; + notify_fingerprint = sentry_value_incref(fingerprint); + } + sentry_value_decref(old_fingerprint); + SENTRY_SCOPE_NOTIFY(scope, set_fingerprint, notify_fingerprint); + sentry_value_decref(notify_fingerprint); } sentry_level_t @@ -1565,8 +1607,9 @@ sentry__scope_apply_to_telemetry(const sentry_scope_t *scope, sentry_value_set_by_key(telemetry, "trace_id", trace_id); } - if (!sentry_value_is_null(scope->user)) { - sentry_value_t user_id = sentry_value_get_by_key(scope->user, "id"); + sentry_value_t user = sentry__scope_ref_user(scope); + if (!sentry_value_is_null(user)) { + sentry_value_t user_id = sentry_value_get_by_key(user, "id"); if (!sentry_value_is_null(user_id)) { sentry_value_incref(user_id); sentry__value_add_attribute( @@ -1574,21 +1617,22 @@ sentry__scope_apply_to_telemetry(const sentry_scope_t *scope, } sentry_value_t user_username - = sentry_value_get_by_key(scope->user, "username"); + = sentry_value_get_by_key(user, "username"); if (!sentry_value_is_null(user_username)) { sentry_value_incref(user_username); sentry__value_add_attribute( attributes, user_username, "string", "user.name"); } - sentry_value_t user_email - = sentry_value_get_by_key(scope->user, "email"); + sentry_value_t user_email = sentry_value_get_by_key(user, "email"); if (!sentry_value_is_null(user_email)) { sentry_value_incref(user_email); sentry__value_add_attribute( attributes, user_email, "string", "user.email"); } } + sentry_value_decref(user); + sentry_value_t os_context = sentry_value_get_by_key(scope->contexts, "os"); if (!sentry_value_is_null(os_context)) { sentry_value_t os_name = sentry_value_get_by_key(os_context, "name"); diff --git a/src/sentry_scope.h b/src/sentry_scope.h index ef8e4aa95b..79dce2b6c7 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -166,12 +166,12 @@ const char *sentry__scope_get_transaction(const sentry_scope_t *scope); void sentry__scope_set_transaction_n( sentry_scope_t *scope, const char *transaction, size_t transaction_len); -sentry_value_t sentry__scope_get_fingerprint(const sentry_scope_t *scope); +sentry_value_t sentry__scope_ref_fingerprint(const sentry_scope_t *scope); void sentry__scope_set_fingerprint_va( sentry_scope_t *scope, const char *fingerprint, va_list va); void sentry__scope_set_fingerprint_nva(sentry_scope_t *scope, const char *fingerprint, size_t fingerprint_len, va_list va); -sentry_value_t sentry__scope_get_user(const sentry_scope_t *scope); +sentry_value_t sentry__scope_ref_user(const sentry_scope_t *scope); sentry_level_t sentry__scope_get_level(const sentry_scope_t *scope); sentry_value_t sentry__scope_add_attachment( diff --git a/src/sentry_session.c b/src/sentry_session.c index 49b90415cc..d340cc7a3f 100644 --- a/src/sentry_session.c +++ b/src/sentry_session.c @@ -220,9 +220,10 @@ sentry_start_session(void) if (options) { options->session = sentry__session_new(scope); if (options->session) { - sentry__session_sync_user(options->session, - sentry__scope_get_user(scope), + sentry_value_t user = sentry__scope_ref_user(scope); + sentry__session_sync_user(options->session, user, options->run ? options->run->installation_id : NULL); + sentry_value_decref(user); sentry__run_write_session(options->run, options->session); } } diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index f84e4f253d..657f7b11fd 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -444,6 +444,16 @@ SENTRY_TEST(scope_fingerprint) TEST_CHECK_JSON_VALUE(sentry_value_get_by_key(event, "fingerprint"), "[\"event1\",\"event2\"]"); + sentry_value_t local_fingerprint + = sentry__scope_ref_fingerprint(local_scope); + sentry_scope_remove_fingerprint(local_scope); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(local_fingerprint), 2); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string( + sentry_value_get_by_index(local_fingerprint, 0)), + "local1"); + sentry_value_decref(local_fingerprint); + sentry_scope_free(local_scope); sentry_value_decref(event); } @@ -661,6 +671,13 @@ SENTRY_TEST(scope_user) TEST_CHECK_JSON_VALUE(sentry_value_get_by_key(event, "user"), "{\"id\":\"3\",\"username\":\"event\"}"); + sentry_value_t local_user = sentry__scope_ref_user(local_scope); + sentry_scope_set_user(local_scope, sentry_value_new_null()); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(local_user, "id")), + "2"); + sentry_value_decref(local_user); + sentry_scope_free(local_scope); sentry_value_decref(event); } @@ -1111,14 +1128,17 @@ SENTRY_TEST(scope_clone) TEST_CHECK(sentry_value_is_null( sentry_value_get_by_key(scope_data, "injected"))); + sentry_value_t scope_user = sentry__scope_ref_user(scope); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - sentry__scope_get_user(scope), "username")), + scope_user, "username")), "original"); - TEST_CHECK(sentry_value_is_null(sentry_value_get_by_key( - sentry__scope_get_user(scope), "injected"))); + TEST_CHECK(sentry_value_is_null( + sentry_value_get_by_key(scope_user, "injected"))); + sentry_value_decref(scope_user); - TEST_CHECK_INT_EQUAL( - sentry_value_get_length(sentry__scope_get_fingerprint(scope)), 2); + sentry_value_t scope_fingerprint = sentry__scope_ref_fingerprint(scope); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(scope_fingerprint), 2); + sentry_value_decref(scope_fingerprint); } sentry_close(); @@ -1505,6 +1525,10 @@ static void observe_set_user(void *data, sentry_value_t user) { test_observer_data_t *d = (test_observer_data_t *)data; + if (!sentry_value_is_null(d->user)) { + sentry_value_decref(d->user); + } + sentry_value_incref(user); d->user = user; d->was_called = true; } @@ -2518,14 +2542,17 @@ SENTRY_TEST(scope_clone_preserves_data) TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( sentry__scope_get_contexts(clone), "device")), "Xbox"); - TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - sentry__scope_get_user(clone), "username")), + sentry_value_t clone_user = sentry__scope_ref_user(clone); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(clone_user, "username")), "alice"); + sentry_value_decref(clone_user); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( sentry__scope_get_extra(clone), "extra_key")), "extra_value"); - TEST_CHECK_INT_EQUAL( - sentry_value_get_length(sentry__scope_get_fingerprint(clone)), 2); + sentry_value_t clone_fingerprint = sentry__scope_ref_fingerprint(clone); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(clone_fingerprint), 2); + sentry_value_decref(clone_fingerprint); TEST_CHECK(sentry__scope_get_level(clone) == SENTRY_LEVEL_WARNING); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key( @@ -2613,7 +2640,9 @@ SENTRY_TEST(scope_clear) TEST_CHECK(sentry_value_get_length(sentry__scope_get_tags(scope)) == 1); TEST_CHECK( sentry_value_get_length(sentry__scope_get_attributes(scope)) == 1); - TEST_CHECK(!sentry_value_is_null(sentry__scope_get_user(scope))); + sentry_value_t scope_user = sentry__scope_ref_user(scope); + TEST_CHECK(!sentry_value_is_null(scope_user)); + sentry_value_decref(scope_user); sentry_scope_clear(scope); @@ -2623,8 +2652,13 @@ SENTRY_TEST(scope_clear) TEST_CHECK(sentry_value_get_length(sentry__scope_get_contexts(scope)) == 0); TEST_CHECK( sentry_value_get_length(sentry__scope_get_attributes(scope)) == 0); - TEST_CHECK(sentry_value_is_null(sentry__scope_get_user(scope))); - TEST_CHECK(sentry_value_is_null(sentry__scope_get_fingerprint(scope))); + scope_user = sentry__scope_ref_user(scope); + TEST_CHECK(sentry_value_is_null(scope_user)); + sentry_value_decref(scope_user); + + sentry_value_t scope_fingerprint = sentry__scope_ref_fingerprint(scope); + TEST_CHECK(sentry_value_is_null(scope_fingerprint)); + sentry_value_decref(scope_fingerprint); TEST_CHECK_INT_EQUAL(sentry__scope_get_level(scope), SENTRY_LEVEL_ERROR); sentry_value_t crumbs = sentry__ringbuffer_to_list(sentry__scope_get_breadcrumbs(scope)); From be7f7033925d01b574e8cd33b25965b33ab1243a Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 5 Aug 2026 22:58:10 +0200 Subject: [PATCH 07/22] refactor(scope): store string fields as values Store release, environment, and transaction as sentry_value_t fields with retained scope accessors. Pass value references through observer notifications and add a generic consuming sentry__value_replace helper for scope value replacement. --- src/sentry_scope.c | 184 ++++++++++++++++++++++++---------------- src/sentry_scope.h | 18 ++-- src/sentry_session.c | 14 ++- src/sentry_value.c | 8 ++ src/sentry_value.h | 6 ++ tests/unit/test_scope.c | 44 ++++++++-- tests/unit/test_value.c | 20 +++++ tests/unit/tests.inc | 1 + 8 files changed, 205 insertions(+), 90 deletions(-) diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 7acf6b1530..611209b46d 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -54,6 +54,13 @@ static sentry_mutex_t g_lock = SENTRY__MUTEX_INIT; = (sentry__rwlock_write_lock(&_locked_scope->rwlock), true); \ _locked_once; _locked_once = false) +#define SENTRY_SCOPE_NOTIFY_OWNED(Scope, Callback, Value) \ + do { \ + sentry_value_t _notify_value = (Value); \ + SENTRY_SCOPE_NOTIFY(Scope, Callback, _notify_value); \ + sentry_value_decref(_notify_value); \ + } while (0) + static sentry_value_t get_client_sdk(void) { @@ -108,9 +115,9 @@ generate_propagation_context(sentry_value_t propagation_context) static void init_scope(sentry_scope_t *scope) { - scope->release = NULL; - scope->environment = NULL; - scope->transaction = NULL; + scope->release = sentry_value_new_null(); + scope->environment = sentry_value_new_null(); + scope->transaction = sentry_value_new_null(); scope->fingerprint = sentry_value_new_null(); scope->user = sentry_value_new_null(); scope->tags = sentry_value_new_object(); @@ -156,9 +163,9 @@ get_scope(void) static void cleanup_scope(sentry_scope_t *scope) { - sentry_free(scope->release); - sentry_free(scope->environment); - sentry_free(scope->transaction); + sentry_value_decref(scope->release); + sentry_value_decref(scope->environment); + sentry_value_decref(scope->transaction); sentry_value_decref(scope->fingerprint); sentry_value_decref(scope->user); sentry_value_decref(scope->tags); @@ -476,9 +483,18 @@ sentry_scope_clone(const sentry_scope_t *scope) sentry__rwlock_init(&clone->rwlock); - clone->release = sentry__string_clone(scope->release); - clone->environment = sentry__string_clone(scope->environment); - clone->transaction = sentry__string_clone(scope->transaction); + sentry_value_t release = sentry__scope_ref_release(scope); + clone->release = sentry__value_clone(release); + sentry_value_decref(release); + + sentry_value_t environment = sentry__scope_ref_environment(scope); + clone->environment = sentry__value_clone(environment); + sentry_value_decref(environment); + + sentry_value_t transaction = sentry__scope_ref_transaction(scope); + clone->transaction = sentry__value_clone(transaction); + sentry_value_decref(transaction); + sentry_value_t fingerprint = sentry__scope_ref_fingerprint(scope); clone->fingerprint = sentry__value_clone(fingerprint); sentry_value_decref(fingerprint); @@ -593,10 +609,9 @@ sentry__scope_update_dsc(sentry_scope_t *scope, const sentry_options_t *options) "sample_rand"); sentry_value_set_by_key(dsc, "sample_rand", sample_rand); sentry_value_incref(sample_rand); + sentry_value_set_by_key(dsc, "release", sentry__scope_ref_release(scope)); sentry_value_set_by_key( - dsc, "release", sentry_value_new_string(scope->release)); - sentry_value_set_by_key( - dsc, "environment", sentry_value_new_string(scope->environment)); + dsc, "environment", sentry__scope_ref_environment(scope)); scope->dynamic_sampling_context = dsc; } @@ -747,6 +762,12 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, SET(Key, sentry_value_new_string(Source)); \ } \ } while (0) +#define PLACE_STRING_VALUE(Key, Source) \ + do { \ + if (IS_NULL(Key) && sentry_value_get_length(Source) > 0) { \ + SET(Key, sentry_value_incref(Source)); \ + } \ + } while (0) #define PLACE_VALUE(Key, Source) \ do { \ if (IS_NULL(Key) && !sentry_value_is_null(Source)) { \ @@ -763,9 +784,15 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, PLACE_STRING("platform", "native"); - PLACE_STRING("release", scope->release); + sentry_value_t release = sentry__scope_ref_release(scope); + PLACE_STRING_VALUE("release", release); + sentry_value_decref(release); + PLACE_STRING("dist", options->dist); - PLACE_STRING("environment", scope->environment); + + sentry_value_t environment = sentry__scope_ref_environment(scope); + PLACE_STRING_VALUE("environment", environment); + sentry_value_decref(environment); // is not transaction and has no level if (IS_NULL("type") && IS_NULL("level")) { @@ -797,7 +824,10 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, PLACE_CLONED_VALUE("fingerprint", fingerprint); sentry_value_decref(fingerprint); - PLACE_STRING("transaction", scope->transaction); + sentry_value_t transaction = sentry__scope_ref_transaction(scope); + PLACE_STRING_VALUE("transaction", transaction); + sentry_value_decref(transaction); + PLACE_VALUE("sdk", scope->client_sdk); sentry_value_t event_tags = sentry_value_get_by_key(event, "tags"); @@ -888,6 +918,7 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, #undef PLACE_CLONED_VALUE #undef PLACE_VALUE +#undef PLACE_STRING_VALUE #undef PLACE_STRING #undef SET #undef IS_NULL @@ -918,17 +949,11 @@ sentry__scope_ref_user(const sentry_scope_t *scope) void sentry_scope_set_user(sentry_scope_t *scope, sentry_value_t user) { - sentry_value_t old_user = sentry_value_new_null(); - sentry_value_t notify_user = sentry_value_new_null(); SCOPE_WRITE_LOCK(scope) { - old_user = scope->user; - scope->user = user; - notify_user = sentry_value_incref(user); + sentry__value_replace(&scope->user, sentry_value_incref(user)); } - sentry_value_decref(old_user); - SENTRY_SCOPE_NOTIFY(scope, set_user, notify_user); - sentry_value_decref(notify_user); + SENTRY_SCOPE_NOTIFY_OWNED(scope, set_user, user); } sentry_value_t @@ -1229,34 +1254,24 @@ sentry_scope_set_fingerprints( return; } - sentry_value_t old_fingerprint = sentry_value_new_null(); - sentry_value_t notify_fingerprint = sentry_value_new_null(); SCOPE_WRITE_LOCK(scope) { - old_fingerprint = scope->fingerprint; - scope->fingerprint = fingerprints; - notify_fingerprint = sentry_value_incref(fingerprints); + sentry__value_replace( + &scope->fingerprint, sentry_value_incref(fingerprints)); } - sentry_value_decref(old_fingerprint); - SENTRY_SCOPE_NOTIFY(scope, set_fingerprint, notify_fingerprint); - sentry_value_decref(notify_fingerprint); + SENTRY_SCOPE_NOTIFY_OWNED(scope, set_fingerprint, fingerprints); } void sentry_scope_remove_fingerprint(sentry_scope_t *scope) { sentry_value_t fingerprint = sentry_value_new_null(); - sentry_value_t old_fingerprint = sentry_value_new_null(); - sentry_value_t notify_fingerprint = sentry_value_new_null(); SCOPE_WRITE_LOCK(scope) { - old_fingerprint = scope->fingerprint; - scope->fingerprint = fingerprint; - notify_fingerprint = sentry_value_incref(fingerprint); + sentry__value_replace( + &scope->fingerprint, sentry_value_incref(fingerprint)); } - sentry_value_decref(old_fingerprint); - SENTRY_SCOPE_NOTIFY(scope, set_fingerprint, notify_fingerprint); - sentry_value_decref(notify_fingerprint); + SENTRY_SCOPE_NOTIFY_OWNED(scope, set_fingerprint, fingerprint); } sentry_level_t @@ -1445,58 +1460,80 @@ sentry__scope_restore_span(sentry_scope_t *scope, sentry_span_t *span) return true; } -const char * -sentry__scope_get_release(const sentry_scope_t *scope) +sentry_value_t +sentry__scope_ref_release(const sentry_scope_t *scope) { - return scope->release; + sentry_value_t release = sentry_value_new_null(); + SCOPE_READ_LOCK(scope) { release = sentry_value_incref(scope->release); } + return release; } void sentry__scope_set_release_n( sentry_scope_t *scope, const char *release, size_t release_len) { - sentry_free(scope->release); - scope->release = sentry__string_clone_n(release, release_len); - sentry_value_set_by_key(scope->dynamic_sampling_context, "release", - sentry_value_new_string(scope->release)); - SENTRY_SCOPE_NOTIFY(scope, set_release, scope->release); + sentry_value_t value = sentry_value_new_string_n(release, release_len); + SCOPE_WRITE_LOCK(scope) + { + sentry__value_replace(&scope->release, sentry_value_incref(value)); + sentry_value_set_by_key(scope->dynamic_sampling_context, "release", + sentry_value_incref(value)); + } + SENTRY_SCOPE_NOTIFY_OWNED(scope, set_release, value); } -const char * -sentry__scope_get_environment(const sentry_scope_t *scope) +sentry_value_t +sentry__scope_ref_environment(const sentry_scope_t *scope) { - return scope->environment; + sentry_value_t environment = sentry_value_new_null(); + SCOPE_READ_LOCK(scope) + { + environment = sentry_value_incref(scope->environment); + } + return environment; } void sentry__scope_set_environment_n( sentry_scope_t *scope, const char *environment, size_t environment_len) { - sentry_free(scope->environment); - scope->environment = sentry__string_clone_n(environment, environment_len); - sentry_value_set_by_key(scope->dynamic_sampling_context, "environment", - sentry_value_new_string(scope->environment)); - SENTRY_SCOPE_NOTIFY(scope, set_environment, scope->environment); + sentry_value_t value + = sentry_value_new_string_n(environment, environment_len); + SCOPE_WRITE_LOCK(scope) + { + sentry__value_replace(&scope->environment, sentry_value_incref(value)); + sentry_value_set_by_key(scope->dynamic_sampling_context, "environment", + sentry_value_incref(value)); + } + SENTRY_SCOPE_NOTIFY_OWNED(scope, set_environment, value); } -const char * -sentry__scope_get_transaction(const sentry_scope_t *scope) +sentry_value_t +sentry__scope_ref_transaction(const sentry_scope_t *scope) { - return scope->transaction; + sentry_value_t transaction = sentry_value_new_null(); + SCOPE_READ_LOCK(scope) + { + transaction = sentry_value_incref(scope->transaction); + } + return transaction; } void sentry__scope_set_transaction_n( sentry_scope_t *scope, const char *transaction, size_t transaction_len) { - sentry_free(scope->transaction); - scope->transaction = sentry__string_clone_n(transaction, transaction_len); - - if (scope->transaction_object) { - sentry_transaction_set_name_n( - scope->transaction_object, transaction, transaction_len); + sentry_value_t value + = sentry_value_new_string_n(transaction, transaction_len); + SCOPE_WRITE_LOCK(scope) + { + sentry__value_replace(&scope->transaction, sentry_value_incref(value)); + if (scope->transaction_object) { + sentry_transaction_set_name_n( + scope->transaction_object, transaction, transaction_len); + } } - SENTRY_SCOPE_NOTIFY(scope, set_transaction, scope->transaction); + SENTRY_SCOPE_NOTIFY_OWNED(scope, set_transaction, value); } sentry_uuid_t @@ -1649,16 +1686,19 @@ sentry__scope_apply_to_telemetry(const sentry_scope_t *scope, attributes, os_version, "string", "os.version"); } } - if (scope->environment) { + sentry_value_t environment = sentry__scope_ref_environment(scope); + if (!sentry_value_is_null(environment)) { sentry__value_add_attribute(attributes, - sentry_value_new_string(scope->environment), "string", - "sentry.environment"); + sentry_value_incref(environment), "string", "sentry.environment"); } - if (scope->release) { - sentry__value_add_attribute(attributes, - sentry_value_new_string(scope->release), "string", - "sentry.release"); + sentry_value_decref(environment); + + sentry_value_t release = sentry__scope_ref_release(scope); + if (!sentry_value_is_null(release)) { + sentry__value_add_attribute(attributes, sentry_value_incref(release), + "string", "sentry.release"); } + sentry_value_decref(release); } sentry_uuid_t diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 79dce2b6c7..ba56cad57a 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -28,9 +28,9 @@ typedef struct sentry_scope_observer_s { void (*clear)(void *data); - void (*set_release)(void *data, const char *release); - void (*set_environment)(void *data, const char *environment); - void (*set_transaction)(void *data, const char *transaction); + void (*set_release)(void *data, sentry_value_t release); + void (*set_environment)(void *data, sentry_value_t environment); + void (*set_transaction)(void *data, sentry_value_t transaction); void (*set_fingerprint)(void *data, sentry_value_t fingerprint); void (*set_level)(void *data, sentry_level_t level); void (*set_user)(void *data, sentry_value_t user); @@ -56,9 +56,9 @@ typedef struct sentry_scope_observer_s { struct sentry_scope_s { sentry_rwlock_t rwlock; - char *release; - char *environment; - char *transaction; + sentry_value_t release; + sentry_value_t environment; + sentry_value_t transaction; sentry_value_t fingerprint; sentry_value_t user; sentry_value_t tags; @@ -154,15 +154,15 @@ void sentry__scope_apply_to_event(const sentry_scope_t *scope, const sentry_options_t *options, sentry_value_t event, sentry_scope_mode_t mode); -const char *sentry__scope_get_release(const sentry_scope_t *scope); +sentry_value_t sentry__scope_ref_release(const sentry_scope_t *scope); void sentry__scope_set_release_n( sentry_scope_t *scope, const char *release, size_t release_len); -const char *sentry__scope_get_environment(const sentry_scope_t *scope); +sentry_value_t sentry__scope_ref_environment(const sentry_scope_t *scope); void sentry__scope_set_environment_n( sentry_scope_t *scope, const char *environment, size_t environment_len); -const char *sentry__scope_get_transaction(const sentry_scope_t *scope); +sentry_value_t sentry__scope_ref_transaction(const sentry_scope_t *scope); void sentry__scope_set_transaction_n( sentry_scope_t *scope, const char *transaction, size_t transaction_len); diff --git a/src/sentry_session.c b/src/sentry_session.c index d340cc7a3f..267365f949 100644 --- a/src/sentry_session.c +++ b/src/sentry_session.c @@ -50,9 +50,17 @@ status_from_string(const char *status) sentry_session_t * sentry__session_new(const sentry_scope_t *scope) { - char *release = sentry__string_clone(sentry__scope_get_release(scope)); - char *environment - = sentry__string_clone(sentry__scope_get_environment(scope)); + sentry_value_t release_value = sentry__scope_ref_release(scope); + char *release = sentry_value_is_null(release_value) + ? NULL + : sentry__string_clone(sentry_value_as_string(release_value)); + sentry_value_decref(release_value); + + sentry_value_t environment_value = sentry__scope_ref_environment(scope); + char *environment = sentry_value_is_null(environment_value) + ? NULL + : sentry__string_clone(sentry_value_as_string(environment_value)); + sentry_value_decref(environment_value); if (!release) { sentry_free(environment); diff --git a/src/sentry_value.c b/src/sentry_value.c index 52332357aa..2cb4e84368 100644 --- a/src/sentry_value.c +++ b/src/sentry_value.c @@ -447,6 +447,14 @@ sentry_value_decref(sentry_value_t value) return thing ? 1 : 0; } +void +sentry__value_replace(sentry_value_t *target, sentry_value_t value) +{ + sentry_value_t old_value = *target; + *target = value; + sentry_value_decref(old_value); +} + size_t sentry_value_refcount(sentry_value_t value) { diff --git a/src/sentry_value.h b/src/sentry_value.h index e6428158d5..bd19c29910 100644 --- a/src/sentry_value.h +++ b/src/sentry_value.h @@ -87,6 +87,12 @@ void sentry__value_foreach_key_value(sentry_value_t value, int sentry__value_set_by_key_owned( sentry_value_t value, char *key, size_t key_len, sentry_value_t v); +/** + * Replaces `*target` with `value`. + * Takes ownership of `value` and releases the previous `*target`. + */ +void sentry__value_replace(sentry_value_t *target, sentry_value_t value); + /** * Removes a value by key and returns the owned object key on success. * The caller must free the returned key. diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 657f7b11fd..617e007559 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -1423,10 +1423,11 @@ deferred_flush_scope( } static void -observe_set_release(void *data, const char *release) +observe_set_release(void *data, sentry_value_t release) { test_observer_data_t *d = (test_observer_data_t *)data; - d->release = sentry_value_new_string(release); + sentry_value_decref(d->release); + d->release = sentry_value_incref(release); d->was_called = true; } @@ -1445,18 +1446,20 @@ observe_clear_wrapped(void *data) } static void -observe_set_environment(void *data, const char *environment) +observe_set_environment(void *data, sentry_value_t environment) { test_observer_data_t *d = (test_observer_data_t *)data; - d->environment = sentry_value_new_string(environment); + sentry_value_decref(d->environment); + d->environment = sentry_value_incref(environment); d->was_called = true; } static void -observe_set_transaction(void *data, const char *transaction) +observe_set_transaction(void *data, sentry_value_t transaction) { test_observer_data_t *d = (test_observer_data_t *)data; - d->transaction = sentry_value_new_string(transaction); + sentry_value_decref(d->transaction); + d->transaction = sentry_value_incref(transaction); d->was_called = true; } @@ -1920,6 +1923,15 @@ SENTRY_TEST(scope_observer_release) TEST_CHECK(d.was_called); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(d.release), "my-release"); + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_value_t release = sentry__scope_ref_release(scope); + sentry__scope_set_release_n( + scope, "next-release", sizeof("next-release") - 1); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(release), "my-release"); + sentry_value_decref(release); + } + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(d.release), "next-release"); + sentry_value_decref(d.release); sentry_close(); } @@ -1942,6 +1954,15 @@ SENTRY_TEST(scope_observer_environment) TEST_CHECK(d.was_called); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(d.environment), "my-env"); + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_value_t environment = sentry__scope_ref_environment(scope); + sentry__scope_set_environment_n( + scope, "next-env", sizeof("next-env") - 1); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(environment), "my-env"); + sentry_value_decref(environment); + } + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(d.environment), "next-env"); + sentry_value_decref(d.environment); sentry_close(); } @@ -1965,6 +1986,17 @@ SENTRY_TEST(scope_observer_transaction) TEST_CHECK_STRING_EQUAL( sentry_value_as_string(d.transaction), "my-transaction"); + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_value_t transaction = sentry__scope_ref_transaction(scope); + sentry__scope_set_transaction_n( + scope, "next-transaction", sizeof("next-transaction") - 1); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(transaction), "my-transaction"); + sentry_value_decref(transaction); + } + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(d.transaction), "next-transaction"); + sentry_value_decref(d.transaction); sentry_close(); } diff --git a/tests/unit/test_value.c b/tests/unit/test_value.c index f461e2f9df..120d19f51b 100644 --- a/tests/unit/test_value.c +++ b/tests/unit/test_value.c @@ -2207,3 +2207,23 @@ SENTRY_TEST(value_refcount) TEST_CHECK_INT_EQUAL(1, sentry_value_refcount(obj)); TEST_CHECK(!sentry_value_decref(obj)); } + +SENTRY_TEST(value_replace) +{ + sentry_value_t target = sentry_value_new_string("old"); + sentry_value_t old = sentry_value_incref(target); + sentry_value_t replacement = sentry_value_new_string("new"); + + sentry__value_replace(&target, replacement); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(target), "new"); + TEST_CHECK_INT_EQUAL(1, sentry_value_refcount(target)); + TEST_CHECK_INT_EQUAL(1, sentry_value_refcount(old)); + + sentry_value_decref(old); + sentry_value_decref(target); + + target = sentry_value_new_object(); + sentry__value_replace(&target, sentry_value_incref(target)); + TEST_CHECK_INT_EQUAL(1, sentry_value_refcount(target)); + sentry_value_decref(target); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 391063f970..2363e711c0 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -505,6 +505,7 @@ XX(value_object_merge_nested) XX(value_object_merge_shallow) XX(value_object_merge_shallow_nested) XX(value_refcount) +XX(value_replace) XX(value_remove_by_null_key) XX(value_set_by_null_key) XX(value_set_stacktrace) From 12d90fa7ab9819acc50ad2e46674cee98aa92a9e Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 6 Aug 2026 10:52:43 +0200 Subject: [PATCH 08/22] refactor(scope): move locked payload into scope data --- src/backends/sentry_backend_native.c | 2 +- src/integrations/sentry_integration_wer.c | 21 +- src/sentry_core.c | 22 +- src/sentry_scope.c | 785 ++++++++++++++-------- src/sentry_scope.h | 38 +- src/sentry_tracing.h | 4 +- tests/unit/test_attachments.c | 3 +- tests/unit/test_basic.c | 3 +- tests/unit/test_scope.c | 12 +- 9 files changed, 548 insertions(+), 342 deletions(-) diff --git a/src/backends/sentry_backend_native.c b/src/backends/sentry_backend_native.c index 0c630ae915..6cfe11a236 100644 --- a/src/backends/sentry_backend_native.c +++ b/src/backends/sentry_backend_native.c @@ -738,7 +738,7 @@ native_backend_write_attachments(const sentry_path_t *event_path) return; } SENTRY_WITH_SCOPE (scope) { - sentry_value_t attachments = scope->attachments; + sentry_value_t attachments = sentry__scope_get_attachments(scope); if (sentry_value_get_length(attachments) == 0) { continue; } diff --git a/src/integrations/sentry_integration_wer.c b/src/integrations/sentry_integration_wer.c index bf1c5eaf93..e2895bc31c 100644 --- a/src/integrations/sentry_integration_wer.c +++ b/src/integrations/sentry_integration_wer.c @@ -207,12 +207,14 @@ wer_clear(void *data) return; } - sentry__value_foreach_key_value(scope->tags, wer_cleanup_tag, wer_data); + sentry__value_foreach_key_value( + sentry__scope_get_tags(scope), wer_cleanup_tag, wer_data); - size_t len = sentry_value_get_length(scope->attachments); + sentry_value_t attachments = sentry__scope_get_attachments(scope); + size_t len = sentry_value_get_length(attachments); for (size_t i = 0; i < len; i++) { wer_remove_attachment( - wer_data, sentry_value_get_by_index(scope->attachments, i)); + wer_data, sentry_value_get_by_index(attachments, i)); } } @@ -238,10 +240,11 @@ register_wer( if (sentry__scope_add_observer(scope, observer)) { wer_data->scope = scope; wer_data->observer = observer; - size_t len = sentry_value_get_length(scope->attachments); + sentry_value_t attachments = sentry__scope_get_attachments(scope); + size_t len = sentry_value_get_length(attachments); for (size_t i = 0; i < len; i++) { wer_add_attachment( - wer_data, sentry_value_get_by_index(scope->attachments, i)); + wer_data, sentry_value_get_by_index(attachments, i)); } } } @@ -256,12 +259,14 @@ unregister_wer( return; } - sentry__value_foreach_key_value(scope->tags, wer_cleanup_tag, wer_data); + sentry__value_foreach_key_value( + sentry__scope_get_tags(scope), wer_cleanup_tag, wer_data); - size_t len = sentry_value_get_length(scope->attachments); + sentry_value_t attachments = sentry__scope_get_attachments(scope); + size_t len = sentry_value_get_length(attachments); for (size_t i = 0; i < len; i++) { wer_remove_attachment( - wer_data, sentry_value_get_by_index(scope->attachments, i)); + wer_data, sentry_value_get_by_index(attachments, i)); } sentry__scope_remove_observer(scope, wer_data->observer); diff --git a/src/sentry_core.c b/src/sentry_core.c index 5dd5822547..6d948a77e8 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -706,9 +706,11 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, } SENTRY_WITH_SCOPE (scope) { - sentry_value_t attachments = scope->attachments; + sentry_value_t attachments = sentry__scope_get_attachments(scope); if (local_scope - && sentry_value_get_length(local_scope->attachments) > 0) { + && sentry_value_get_length( + sentry__scope_get_attachments(local_scope)) + > 0) { // all attachments merged from multiple scopes sentry__attachments_extend(&all_attachments, sentry__scope_get_attachments(local_scope)); @@ -846,13 +848,15 @@ prepare_user_feedback(const sentry_options_t *options, sentry__attachments_extend(&all_attachments, hint->attachments); } if (local_scope) { - sentry__attachments_extend(&all_attachments, local_scope->attachments); + sentry__attachments_extend( + &all_attachments, sentry__scope_get_attachments(local_scope)); } SENTRY_WITH_SCOPE (scope) { - sentry_value_t attachments = scope->attachments; + sentry_value_t attachments = sentry__scope_get_attachments(scope); if (sentry_value_get_length(all_attachments) > 0) { - sentry__attachments_extend(&all_attachments, scope->attachments); + sentry__attachments_extend( + &all_attachments, sentry__scope_get_attachments(scope)); attachments = all_attachments; } sentry__envelope_add_attachments(envelope, attachments, options); @@ -2044,8 +2048,8 @@ sentry_clear_attachments(void) { SENTRY_WITH_OPTIONS (options) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_t attachments = scope->attachments; - scope->attachments = sentry__attachments_new(); + sentry_value_t attachments + = sentry__scope_take_attachments(scope); size_t len = sentry_value_get_length(attachments); for (size_t i = 0; i < len; i++) { sentry_value_t attachment @@ -2071,8 +2075,8 @@ sentry_remove_attachment(sentry_uuid_t attachment_id) SENTRY_WITH_OPTIONS (options) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_t removed = sentry__attachments_remove( - scope->attachments, &attachment_id); + sentry_value_t removed + = sentry__scope_remove_attachment(scope, &attachment_id); if (!sentry_value_is_null(removed)) { if (options->backend && options->backend->remove_attachment_func) { diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 611209b46d..6ee7480d9d 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -29,29 +29,65 @@ #endif static bool g_scope_initialized = false; -static sentry_scope_t g_scope = { 0 }; #ifdef SENTRY__MUTEX_INIT_DYN SENTRY__MUTEX_INIT_DYN(g_lock) #else static sentry_mutex_t g_lock = SENTRY__MUTEX_INIT; #endif +struct sentry_scope_data_s { + sentry_rwlock_t rwlock; + + sentry_value_t release; + sentry_value_t environment; + sentry_value_t transaction; + sentry_value_t fingerprint; + sentry_value_t user; + sentry_value_t tags; + sentry_value_t extra; + sentry_value_t attributes; + sentry_value_t contexts; + sentry_value_t propagation_context; + sentry_ringbuffer_t *breadcrumbs; + sentry_value_t dynamic_sampling_context; + sentry_level_t level; + sentry_uuid_t last_event_id; + sentry_value_t client_sdk; + sentry_value_t attachments; + + // The span attached to this scope, if any. + // + // Conceptually, every transaction is a span, so it should be possible to + // attach spans or transactions to a scope. But sentry_span_t and + // sentry_transaction_t are unrelated types in the native SDK, so we need + // two distinct pointers. At most one of them should ever be non-null. + // Whenever possible, `transaction` should pull its value from the + // `name` property nested in transaction_object or span. + sentry_transaction_t *transaction_object; + sentry_span_t *span; + bool trace_managed; +}; + +static sentry_scope_t g_scope = { 0 }; +static sentry_scope_data_t g_scope_data = { 0 }; + #define SCOPE_READ_LOCK(Scope) \ - for (const sentry_scope_t *_locked_scope = (Scope); _locked_scope; \ - sentry__rwlock_read_unlock((sentry_rwlock_t *)&_locked_scope->rwlock), \ - _locked_scope = NULL) \ + for (const sentry_scope_data_t *_locked_data = (Scope)->data; \ + _locked_data; \ + sentry__rwlock_read_unlock((sentry_rwlock_t *)&_locked_data->rwlock), \ + _locked_data = NULL) \ for (bool _locked_once \ = (sentry__rwlock_read_lock( \ - (sentry_rwlock_t *)&_locked_scope->rwlock), \ + (sentry_rwlock_t *)&_locked_data->rwlock), \ true); \ _locked_once; _locked_once = false) #define SCOPE_WRITE_LOCK(Scope) \ - for (sentry_scope_t *_locked_scope = (Scope); _locked_scope; \ - sentry__rwlock_write_unlock(&_locked_scope->rwlock), \ - _locked_scope = NULL) \ + for (sentry_scope_data_t *_locked_data = (Scope)->data; _locked_data; \ + sentry__rwlock_write_unlock(&_locked_data->rwlock), \ + _locked_data = NULL) \ for (bool _locked_once \ - = (sentry__rwlock_write_lock(&_locked_scope->rwlock), true); \ + = (sentry__rwlock_write_lock(&_locked_data->rwlock), true); \ _locked_once; _locked_once = false) #define SENTRY_SCOPE_NOTIFY_OWNED(Scope, Callback, Value) \ @@ -95,6 +131,61 @@ get_client_sdk(void) return client_sdk; } +static bool +data_set_by_key(sentry_scope_data_t *data, sentry_value_t *object, + const char *key, sentry_value_t value) +{ + sentry__rwlock_write_lock(&data->rwlock); + int rv = sentry_value_set_by_key(*object, key, value); + sentry__rwlock_write_unlock(&data->rwlock); + return rv == 0; +} + +static bool +data_set_by_key_n(sentry_scope_data_t *data, sentry_value_t *object, + const char *key, size_t key_len, sentry_value_t value) +{ + sentry__rwlock_write_lock(&data->rwlock); + int rv = sentry_value_set_by_key_n(*object, key, key_len, value); + sentry__rwlock_write_unlock(&data->rwlock); + return rv == 0; +} + +static bool +data_remove_by_key( + sentry_scope_data_t *data, sentry_value_t *object, const char *key) +{ + sentry__rwlock_write_lock(&data->rwlock); + int rv = sentry_value_remove_by_key(*object, key); + sentry__rwlock_write_unlock(&data->rwlock); + return rv == 0; +} + +static char * +data_remove_and_take_key_n(sentry_scope_data_t *data, sentry_value_t *object, + const char *key, size_t key_len) +{ + sentry__rwlock_write_lock(&data->rwlock); + char *rv = sentry__value_remove_and_take_key_n(*object, key, key_len); + sentry__rwlock_write_unlock(&data->rwlock); + return rv; +} + +static bool +data_update_context_by_key_n(sentry_scope_data_t *data, const char *key, + size_t key_len, sentry_value_t value) +{ + sentry__rwlock_write_lock(&data->rwlock); + sentry_value_t context + = sentry_value_get_by_key_n(data->contexts, key, key_len); + if (!sentry_value_is_null(context)) { + sentry__value_merge_objects(value, context); + } + int rv = sentry_value_set_by_key_n(data->contexts, key, key_len, value); + sentry__rwlock_write_unlock(&data->rwlock); + return rv == 0; +} + static void generate_propagation_context(sentry_value_t propagation_context) { @@ -113,32 +204,85 @@ generate_propagation_context(sentry_value_t propagation_context) } static void -init_scope(sentry_scope_t *scope) -{ - scope->release = sentry_value_new_null(); - scope->environment = sentry_value_new_null(); - scope->transaction = sentry_value_new_null(); - scope->fingerprint = sentry_value_new_null(); - scope->user = sentry_value_new_null(); - scope->tags = sentry_value_new_object(); - scope->extra = sentry_value_new_object(); - scope->attributes = sentry_value_new_object(); - scope->contexts = sentry_value_new_object(); - scope->propagation_context = sentry_value_new_object(); - scope->breadcrumbs = sentry__ringbuffer_new(SENTRY_BREADCRUMBS_MAX); - scope->dynamic_sampling_context = sentry_value_new_object(); - scope->level = SENTRY_LEVEL_ERROR; - scope->last_event_id = sentry_uuid_nil(); - scope->client_sdk = sentry_value_new_null(); - scope->attachments = sentry__attachments_new(); - scope->transaction_object = NULL; - scope->span = NULL; - scope->trace_managed = true; +init_data(sentry_scope_data_t *data) +{ + data->release = sentry_value_new_null(); + data->environment = sentry_value_new_null(); + data->transaction = sentry_value_new_null(); + data->fingerprint = sentry_value_new_null(); + data->user = sentry_value_new_null(); + data->tags = sentry_value_new_object(); + data->extra = sentry_value_new_object(); + data->attributes = sentry_value_new_object(); + data->contexts = sentry_value_new_object(); + data->propagation_context = sentry_value_new_object(); + data->breadcrumbs = sentry__ringbuffer_new(SENTRY_BREADCRUMBS_MAX); + data->dynamic_sampling_context = sentry_value_new_object(); + data->level = SENTRY_LEVEL_ERROR; + data->last_event_id = sentry_uuid_nil(); + data->client_sdk = sentry_value_new_null(); + data->attachments = sentry__attachments_new(); + data->transaction_object = NULL; + data->span = NULL; + data->trace_managed = true; +} + +static void +cleanup_data(sentry_scope_data_t *data) +{ + sentry_value_decref(data->release); + sentry_value_decref(data->environment); + sentry_value_decref(data->transaction); + sentry_value_decref(data->fingerprint); + sentry_value_decref(data->user); + sentry_value_decref(data->tags); + sentry_value_decref(data->extra); + sentry_value_decref(data->attributes); + sentry_value_decref(data->contexts); + sentry_value_decref(data->propagation_context); + sentry__ringbuffer_free(data->breadcrumbs); + sentry_value_decref(data->dynamic_sampling_context); + sentry_value_decref(data->client_sdk); + sentry_value_decref(data->attachments); + sentry__transaction_decref(data->transaction_object); + sentry__span_decref(data->span); +} + +static sentry_scope_data_t * +new_data(void) +{ + sentry_scope_data_t *data = SENTRY_MAKE(sentry_scope_data_t); + if (data) { + sentry__rwlock_init(&data->rwlock); + init_data(data); + } + return data; +} + +static void +free_data(sentry_scope_data_t *data) +{ + if (!data) { + return; + } + cleanup_data(data); + sentry__rwlock_free(&data->rwlock); + sentry_free(data); +} + +static bool +init_scope(sentry_scope_t *scope, sentry_scope_data_t *data) +{ + scope->data = data ? data : new_data(); + if (!scope->data) { + return false; + } scope->observers = NULL; scope->num_observers = 0; scope->is_notifying = 0; scope->pending_flush = false; scope->one_shot = false; + return true; } static sentry_scope_t * @@ -149,11 +293,16 @@ get_scope(void) } memset(&g_scope, 0, sizeof(sentry_scope_t)); - sentry__rwlock_init(&g_scope.rwlock); - init_scope(&g_scope); - g_scope.user = sentry_value_new_object(); - sentry_value_set_by_key(g_scope.contexts, "os", sentry__get_os_context()); - g_scope.client_sdk = get_client_sdk(); + memset(&g_scope_data, 0, sizeof(sentry_scope_data_t)); + sentry__rwlock_init(&g_scope_data.rwlock); + init_data(&g_scope_data); + if (!init_scope(&g_scope, &g_scope_data)) { + return &g_scope; + } + g_scope.data->user = sentry_value_new_object(); + sentry_value_set_by_key( + g_scope.data->contexts, "os", sentry__get_os_context()); + g_scope.data->client_sdk = get_client_sdk(); g_scope_initialized = true; @@ -161,24 +310,8 @@ get_scope(void) } static void -cleanup_scope(sentry_scope_t *scope) +cleanup_observers(sentry_scope_t *scope) { - sentry_value_decref(scope->release); - sentry_value_decref(scope->environment); - sentry_value_decref(scope->transaction); - sentry_value_decref(scope->fingerprint); - sentry_value_decref(scope->user); - sentry_value_decref(scope->tags); - sentry_value_decref(scope->extra); - sentry_value_decref(scope->attributes); - sentry_value_decref(scope->contexts); - sentry_value_decref(scope->propagation_context); - sentry__ringbuffer_free(scope->breadcrumbs); - sentry_value_decref(scope->dynamic_sampling_context); - sentry_value_decref(scope->client_sdk); - sentry_value_decref(scope->attachments); - sentry__transaction_decref(scope->transaction_object); - sentry__span_decref(scope->span); for (size_t i = 0; i < scope->num_observers; i++) { sentry_free(scope->observers[i]); } @@ -188,6 +321,14 @@ cleanup_scope(sentry_scope_t *scope) scope->pending_flush = false; } +static void +cleanup_scope(sentry_scope_t *scope) +{ + free_data(scope->data); + scope->data = NULL; + cleanup_observers(scope); +} + void sentry__scope_cleanup(void) { @@ -195,8 +336,10 @@ sentry__scope_cleanup(void) sentry__mutex_lock(&g_lock); if (g_scope_initialized) { g_scope_initialized = false; - cleanup_scope(&g_scope); - sentry__rwlock_free(&g_scope.rwlock); + cleanup_data(g_scope.data); + sentry__rwlock_free(&g_scope.data->rwlock); + g_scope.data = NULL; + cleanup_observers(&g_scope); } sentry__mutex_unlock(&g_lock); } @@ -351,8 +494,10 @@ sentry_scope_new(void) return NULL; } - sentry__rwlock_init(&scope->rwlock); - init_scope(scope); + if (!init_scope(scope, NULL)) { + sentry_free(scope); + return NULL; + } return scope; } @@ -364,22 +509,22 @@ sentry_scope_free(sentry_scope_t *scope) } cleanup_scope(scope); - sentry__rwlock_free(&scope->rwlock); sentry_free(scope); } bool sentry__scope_is_one_shot(const sentry_scope_t *scope) { - bool one_shot = false; - SCOPE_READ_LOCK(scope) { one_shot = scope->one_shot; } - return one_shot; + return scope && scope->one_shot; } void sentry__scope_set_one_shot(sentry_scope_t *scope, bool one_shot) { - SCOPE_WRITE_LOCK(scope) { scope->one_shot = one_shot; } + if (!scope) { + return; + } + scope->one_shot = one_shot; } void @@ -405,19 +550,19 @@ sentry__scope_apply_options(sentry_scope_t *scope, sentry_options_t *options) { if (options->sdk_name) { sentry_value_t sdk_name = sentry_value_new_string(options->sdk_name); - sentry_value_set_by_key(scope->client_sdk, "name", sdk_name); + sentry_value_set_by_key(scope->data->client_sdk, "name", sdk_name); } - sentry_value_freeze(scope->client_sdk); - generate_propagation_context(scope->propagation_context); + sentry_value_freeze(scope->data->client_sdk); + generate_propagation_context(scope->data->propagation_context); sentry__scope_set_release_n( scope, options->release, sentry__guarded_strlen(options->release)); sentry__scope_set_environment_n(scope, options->environment, sentry__guarded_strlen(options->environment)); - scope->attachments = options->attachments; + scope->data->attachments = options->attachments; options->attachments = NULL; sentry__ringbuffer_set_max_size( - scope->breadcrumbs, options->max_breadcrumbs); + scope->data->breadcrumbs, options->max_breadcrumbs); sentry__scope_update_dsc(scope, options); } @@ -438,35 +583,25 @@ sentry_scope_clear(sentry_scope_t *scope) } sentry__scope_end_notify(scope); - sentry_scope_observer_t **observers = scope->observers; - size_t num_observers = scope->num_observers; - size_t is_notifying = scope->is_notifying; - bool pending_flush = scope->pending_flush; - scope->observers = NULL; - scope->num_observers = 0; - // Keep the propagation and dynamic sampling contexts across clears so // telemetry captured afterwards continues on the same trace. - bool trace_managed = sentry__scope_is_trace_managed(scope); - sentry_value_t propagation_context = scope->propagation_context; - sentry_value_t dynamic_sampling_context = scope->dynamic_sampling_context; - sentry_value_incref(propagation_context); - sentry_value_incref(dynamic_sampling_context); - bool one_shot = sentry__scope_is_one_shot(scope); + sentry_scope_data_t *data = scope->data; + sentry__rwlock_write_lock(&data->rwlock); + bool trace_managed = data->trace_managed; + sentry_value_t propagation_context + = sentry_value_incref(data->propagation_context); + sentry_value_t dynamic_sampling_context + = sentry_value_incref(data->dynamic_sampling_context); - cleanup_scope(scope); - init_scope(scope); + cleanup_data(data); + init_data(data); - sentry_value_decref(scope->propagation_context); - sentry_value_decref(scope->dynamic_sampling_context); - scope->propagation_context = propagation_context; - scope->dynamic_sampling_context = dynamic_sampling_context; - sentry__scope_set_trace_managed(scope, trace_managed); - sentry__scope_set_one_shot(scope, one_shot); - scope->observers = observers; - scope->num_observers = num_observers; - scope->is_notifying = is_notifying; - scope->pending_flush = pending_flush; + sentry_value_decref(data->propagation_context); + sentry_value_decref(data->dynamic_sampling_context); + data->propagation_context = propagation_context; + data->dynamic_sampling_context = dynamic_sampling_context; + data->trace_managed = trace_managed; + sentry__rwlock_write_unlock(&data->rwlock); } sentry_scope_t * @@ -481,50 +616,48 @@ sentry_scope_clone(const sentry_scope_t *scope) return NULL; } - sentry__rwlock_init(&clone->rwlock); - - sentry_value_t release = sentry__scope_ref_release(scope); - clone->release = sentry__value_clone(release); - sentry_value_decref(release); - - sentry_value_t environment = sentry__scope_ref_environment(scope); - clone->environment = sentry__value_clone(environment); - sentry_value_decref(environment); - - sentry_value_t transaction = sentry__scope_ref_transaction(scope); - clone->transaction = sentry__value_clone(transaction); - sentry_value_decref(transaction); + clone->data = SENTRY_MAKE(sentry_scope_data_t); + if (!clone->data) { + sentry_free(clone); + return NULL; + } + sentry__rwlock_init(&clone->data->rwlock); - sentry_value_t fingerprint = sentry__scope_ref_fingerprint(scope); - clone->fingerprint = sentry__value_clone(fingerprint); - sentry_value_decref(fingerprint); + SCOPE_READ_LOCK(scope) + { + clone->data->release = sentry__value_clone(scope->data->release); + clone->data->environment + = sentry__value_clone(scope->data->environment); + clone->data->transaction + = sentry__value_clone(scope->data->transaction); + clone->data->fingerprint + = sentry__value_clone(scope->data->fingerprint); + clone->data->user = sentry__value_clone(scope->data->user); + clone->data->tags = sentry__value_clone(scope->data->tags); + clone->data->extra = sentry__value_clone(scope->data->extra); + clone->data->contexts = sentry__value_clone(scope->data->contexts); + clone->data->attributes = sentry__value_clone(scope->data->attributes); + clone->data->propagation_context + = sentry__value_clone(scope->data->propagation_context); + clone->data->breadcrumbs + = sentry__ringbuffer_clone(scope->data->breadcrumbs); + clone->data->dynamic_sampling_context + = sentry__value_clone(scope->data->dynamic_sampling_context); + if (sentry_value_is_frozen(scope->data->dynamic_sampling_context)) { + sentry_value_freeze(clone->data->dynamic_sampling_context); + } + clone->data->level = scope->data->level; + clone->data->last_event_id = scope->data->last_event_id; + clone->data->client_sdk = sentry__value_clone(scope->data->client_sdk); + sentry__attachments_extend( + &clone->data->attachments, scope->data->attachments); - sentry_value_t user = sentry__scope_ref_user(scope); - clone->user = sentry__value_clone(user); - sentry_value_decref(user); - clone->tags = sentry__value_clone(scope->tags); - clone->extra = sentry__value_clone(scope->extra); - clone->attributes = sentry__value_clone(scope->attributes); - clone->contexts = sentry__value_clone(scope->contexts); - clone->propagation_context - = sentry__value_clone(scope->propagation_context); - clone->breadcrumbs = sentry__ringbuffer_clone(scope->breadcrumbs); - clone->dynamic_sampling_context - = sentry__value_clone(scope->dynamic_sampling_context); - if (sentry_value_is_frozen(scope->dynamic_sampling_context)) { - sentry_value_freeze(clone->dynamic_sampling_context); - } - clone->level = sentry__scope_get_level(scope); - clone->last_event_id = sentry_scope_get_last_event_id(scope); - clone->client_sdk = sentry__value_clone(scope->client_sdk); - clone->attachments = sentry__attachments_new(); - sentry__attachments_extend(&clone->attachments, scope->attachments); - - clone->transaction_object = scope->transaction_object; - sentry__transaction_incref(clone->transaction_object); - clone->span = scope->span; - sentry__span_incref(clone->span); - clone->trace_managed = sentry__scope_is_trace_managed(scope); + clone->data->transaction_object = scope->data->transaction_object; + sentry__transaction_incref(clone->data->transaction_object); + clone->data->span = scope->data->span; + sentry__span_incref(clone->data->span); + clone->data->trace_managed = scope->data->trace_managed; + } return clone; } @@ -532,62 +665,62 @@ sentry_scope_clone(const sentry_scope_t *scope) sentry_value_t sentry__scope_get_propagation_context(const sentry_scope_t *scope) { - return scope->propagation_context; + return scope->data->propagation_context; } sentry_value_t sentry__scope_get_trace_context(const sentry_scope_t *scope) { - return sentry_value_get_by_key(scope->propagation_context, "trace"); + return sentry_value_get_by_key(scope->data->propagation_context, "trace"); } void sentry__scope_set_propagation_context( sentry_scope_t *scope, const char *key, sentry_value_t value) { - sentry_value_set_by_key(scope->propagation_context, key, value); + sentry_value_set_by_key(scope->data->propagation_context, key, value); } void sentry__scope_regenerate_propagation_context(sentry_scope_t *scope) { - generate_propagation_context(scope->propagation_context); + generate_propagation_context(scope->data->propagation_context); } bool sentry__scope_is_trace_managed(const sentry_scope_t *scope) { bool managed = false; - SCOPE_READ_LOCK(scope) { managed = scope->trace_managed; } + SCOPE_READ_LOCK(scope) { managed = scope->data->trace_managed; } return managed; } void sentry__scope_set_trace_managed(sentry_scope_t *scope, bool managed) { - SCOPE_WRITE_LOCK(scope) { scope->trace_managed = managed; } + SCOPE_WRITE_LOCK(scope) { scope->data->trace_managed = managed; } } sentry_value_t sentry__scope_get_dsc(const sentry_scope_t *scope) { - return scope->dynamic_sampling_context; + return scope->data->dynamic_sampling_context; } void sentry__scope_freeze_dsc(sentry_scope_t *scope, sentry_value_t incoming) { - sentry_value_decref(scope->dynamic_sampling_context); + sentry_value_decref(scope->data->dynamic_sampling_context); sentry_value_t dsc = sentry_value_new_object(); sentry__value_merge_objects(dsc, incoming); sentry_value_freeze(dsc); - scope->dynamic_sampling_context = dsc; + scope->data->dynamic_sampling_context = dsc; } void sentry__scope_update_dsc(sentry_scope_t *scope, const sentry_options_t *options) { - sentry_value_decref(scope->dynamic_sampling_context); + sentry_value_decref(scope->data->dynamic_sampling_context); sentry_value_t dsc = sentry_value_new_object(); if (options->dsn) { @@ -605,7 +738,7 @@ sentry__scope_update_dsc(sentry_scope_t *scope, const sentry_options_t *options) dsc, "sample_rate", sentry_value_new_double(1.0)); } sentry_value_t sample_rand = sentry_value_get_by_key( - sentry_value_get_by_key(scope->propagation_context, "trace"), + sentry_value_get_by_key(scope->data->propagation_context, "trace"), "sample_rand"); sentry_value_set_by_key(dsc, "sample_rand", sample_rand); sentry_value_incref(sample_rand); @@ -613,7 +746,7 @@ sentry__scope_update_dsc(sentry_scope_t *scope, const sentry_options_t *options) sentry_value_set_by_key( dsc, "environment", sentry__scope_ref_environment(scope)); - scope->dynamic_sampling_context = dsc; + scope->data->dynamic_sampling_context = dsc; } #if !defined(SENTRY_PLATFORM_NX) @@ -722,10 +855,10 @@ sentry__symbolize_stacktrace(sentry_value_t stacktrace) static sentry_value_t get_span_or_transaction(const sentry_scope_t *scope) { - if (scope->span) { - return scope->span->inner; - } else if (scope->transaction_object) { - return scope->transaction_object->inner; + if (scope->data->span) { + return scope->data->span->inner; + } else if (scope->data->transaction_object) { + return scope->data->transaction_object->inner; } else { return sentry_value_new_null(); } @@ -828,27 +961,35 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, PLACE_STRING_VALUE("transaction", transaction); sentry_value_decref(transaction); - PLACE_VALUE("sdk", scope->client_sdk); + PLACE_VALUE("sdk", scope->data->client_sdk); - sentry_value_t event_tags = sentry_value_get_by_key(event, "tags"); - if (sentry_value_is_null(event_tags)) { - if (!sentry_value_is_null(scope->tags)) { - PLACE_CLONED_VALUE("tags", scope->tags); + SCOPE_READ_LOCK(scope) + { + sentry_value_t event_tags = sentry_value_get_by_key(event, "tags"); + if (sentry_value_is_null(event_tags)) { + if (!sentry_value_is_null(scope->data->tags)) { + PLACE_CLONED_VALUE("tags", scope->data->tags); + } + } else { + sentry__value_merge_objects(event_tags, scope->data->tags); } - } else { - sentry__value_merge_objects(event_tags, scope->tags); - } - sentry_value_t event_extra = sentry_value_get_by_key(event, "extra"); - if (sentry_value_is_null(event_extra)) { - if (!sentry_value_is_null(scope->extra)) { - PLACE_CLONED_VALUE("extra", scope->extra); + + sentry_value_t event_extra = sentry_value_get_by_key(event, "extra"); + if (sentry_value_is_null(event_extra)) { + if (!sentry_value_is_null(scope->data->extra)) { + PLACE_CLONED_VALUE("extra", scope->data->extra); + } + } else { + sentry__value_merge_objects(event_extra, scope->data->extra); } - } else { - sentry__value_merge_objects(event_extra, scope->extra); } bool is_transaction = sentry__event_is_transaction(event); - sentry_value_t contexts = sentry__value_clone(scope->contexts); + sentry_value_t contexts = sentry_value_new_null(); + SCOPE_READ_LOCK(scope) + { + contexts = sentry__value_clone(scope->data->contexts); + } if (is_transaction && !sentry_value_is_null(contexts)) { sentry_value_remove_by_key(contexts, "trace"); } @@ -881,7 +1022,7 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, if (!is_transaction && sentry_value_is_null(scope_trace) && sentry_value_is_null( sentry_value_get_by_key(event_contexts, "trace"))) { - sentry__value_merge_objects(contexts, scope->propagation_context); + sentry__value_merge_objects(contexts, scope->data->propagation_context); } if (sentry_value_is_null(event_contexts)) { PLACE_VALUE("contexts", contexts); @@ -894,7 +1035,7 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, sentry_value_t event_breadcrumbs = sentry_value_get_by_key(event, "breadcrumbs"); sentry_value_t scope_breadcrumbs - = sentry__ringbuffer_to_list(scope->breadcrumbs); + = sentry__ringbuffer_to_list(scope->data->breadcrumbs); sentry_value_set_by_key(event, "breadcrumbs", sentry__value_merge_breadcrumbs(event_breadcrumbs, scope_breadcrumbs, options->max_breadcrumbs)); @@ -927,7 +1068,7 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, void sentry_scope_add_breadcrumb(sentry_scope_t *scope, sentry_value_t breadcrumb) { - if (sentry__ringbuffer_append(scope->breadcrumbs, breadcrumb) == 0) { + if (sentry__ringbuffer_append(scope->data->breadcrumbs, breadcrumb) == 0) { SENTRY_SCOPE_NOTIFY(scope, add_breadcrumb, breadcrumb); } } @@ -935,14 +1076,14 @@ sentry_scope_add_breadcrumb(sentry_scope_t *scope, sentry_value_t breadcrumb) const sentry_ringbuffer_t * sentry__scope_get_breadcrumbs(const sentry_scope_t *scope) { - return scope->breadcrumbs; + return scope->data->breadcrumbs; } sentry_value_t sentry__scope_ref_user(const sentry_scope_t *scope) { sentry_value_t user = sentry_value_new_null(); - SCOPE_READ_LOCK(scope) { user = sentry_value_incref(scope->user); } + SCOPE_READ_LOCK(scope) { user = sentry_value_incref(scope->data->user); } return user; } @@ -951,7 +1092,7 @@ sentry_scope_set_user(sentry_scope_t *scope, sentry_value_t user) { SCOPE_WRITE_LOCK(scope) { - sentry__value_replace(&scope->user, sentry_value_incref(user)); + sentry__value_replace(&scope->data->user, sentry_value_incref(user)); } SENTRY_SCOPE_NOTIFY_OWNED(scope, set_user, user); } @@ -959,15 +1100,14 @@ sentry_scope_set_user(sentry_scope_t *scope, sentry_value_t user) sentry_value_t sentry__scope_get_tags(const sentry_scope_t *scope) { - return scope->tags; + return scope->data->tags; } void sentry_scope_set_tag(sentry_scope_t *scope, const char *key, const char *value) { - if (sentry_value_set_by_key( - scope->tags, key, sentry_value_new_string(value)) - == 0) { + if (data_set_by_key(scope->data, &scope->data->tags, key, + sentry_value_new_string(value))) { SENTRY_SCOPE_NOTIFY(scope, set_tag, key, value); } } @@ -976,11 +1116,20 @@ void sentry_scope_set_tag_n(sentry_scope_t *scope, const char *key, size_t key_len, const char *value, size_t value_len) { - char *k = sentry__string_clone_n(key, key_len); - sentry_value_t v = sentry_value_new_string_n(value, value_len); - if (sentry__value_set_by_key_owned(scope->tags, k, key_len, v) == 0) { - SENTRY_SCOPE_NOTIFY(scope, set_tag, k, sentry_value_as_string(v)); + sentry_value_t tag_value = sentry_value_new_string_n(value, value_len); + char *notify_key = sentry__string_clone_n(key, key_len); + if (!notify_key) { + sentry_value_decref(tag_value); + return; } + + if (data_set_by_key_n(scope->data, &scope->data->tags, key, key_len, + sentry_value_incref(tag_value))) { + SENTRY_SCOPE_NOTIFY( + scope, set_tag, notify_key, sentry_value_as_string(tag_value)); + } + sentry_free(notify_key); + sentry_value_decref(tag_value); } static void @@ -1004,7 +1153,7 @@ sentry_scope_set_tags(sentry_scope_t *scope, sentry_value_t tags) void sentry__scope_remove_tag(sentry_scope_t *scope, const char *key) { - if (sentry_value_remove_by_key(scope->tags, key) == 0) { + if (data_remove_by_key(scope->data, &scope->data->tags, key)) { SENTRY_SCOPE_NOTIFY(scope, remove_tag, key); } } @@ -1013,7 +1162,8 @@ void sentry__scope_remove_tag_n( sentry_scope_t *scope, const char *key, size_t key_len) { - char *k = sentry__value_remove_and_take_key_n(scope->tags, key, key_len); + char *k = data_remove_and_take_key_n( + scope->data, &scope->data->tags, key, key_len); if (k) { SENTRY_SCOPE_NOTIFY(scope, remove_tag, k); } @@ -1023,32 +1173,42 @@ sentry__scope_remove_tag_n( sentry_value_t sentry__scope_get_extra(const sentry_scope_t *scope) { - return scope->extra; + return scope->data->extra; } void sentry_scope_set_extra( sentry_scope_t *scope, const char *key, sentry_value_t value) { - if (sentry_value_set_by_key(scope->extra, key, value) == 0) { + if (data_set_by_key(scope->data, &scope->data->extra, key, + sentry_value_incref(value))) { SENTRY_SCOPE_NOTIFY(scope, set_extra, key, value); } + sentry_value_decref(value); } void sentry_scope_set_extra_n(sentry_scope_t *scope, const char *key, size_t key_len, sentry_value_t value) { - char *k = sentry__string_clone_n(key, key_len); - if (sentry__value_set_by_key_owned(scope->extra, k, key_len, value) == 0) { - SENTRY_SCOPE_NOTIFY(scope, set_extra, k, value); + char *notify_key = sentry__string_clone_n(key, key_len); + if (!notify_key) { + sentry_value_decref(value); + return; } + + if (data_set_by_key_n(scope->data, &scope->data->extra, key, key_len, + sentry_value_incref(value))) { + SENTRY_SCOPE_NOTIFY(scope, set_extra, notify_key, value); + } + sentry_free(notify_key); + sentry_value_decref(value); } void sentry__scope_remove_extra(sentry_scope_t *scope, const char *key) { - if (sentry_value_remove_by_key(scope->extra, key) == 0) { + if (data_remove_by_key(scope->data, &scope->data->extra, key)) { SENTRY_SCOPE_NOTIFY(scope, remove_extra, key); } } @@ -1057,7 +1217,8 @@ void sentry__scope_remove_extra_n( sentry_scope_t *scope, const char *key, size_t key_len) { - char *k = sentry__value_remove_and_take_key_n(scope->extra, key, key_len); + char *k = data_remove_and_take_key_n( + scope->data, &scope->data->extra, key, key_len); if (k) { SENTRY_SCOPE_NOTIFY(scope, remove_extra, k); } @@ -1082,52 +1243,61 @@ sentry_scope_set_attribute_n(sentry_scope_t *scope, const char *key, sentry_value_decref(attribute); return; } - sentry_value_set_by_key_n(scope->attributes, key, key_len, attribute); + sentry_value_set_by_key_n(scope->data->attributes, key, key_len, attribute); } sentry_value_t sentry__scope_get_attributes(const sentry_scope_t *scope) { - return scope->attributes; + return scope->data->attributes; } void sentry_scope_remove_attribute(sentry_scope_t *scope, const char *key) { - sentry_value_remove_by_key(scope->attributes, key); + sentry_value_remove_by_key(scope->data->attributes, key); } void sentry_scope_remove_attribute_n( sentry_scope_t *scope, const char *key, size_t key_len) { - sentry_value_remove_by_key_n(scope->attributes, key, key_len); + sentry_value_remove_by_key_n(scope->data->attributes, key, key_len); } sentry_value_t sentry__scope_get_contexts(const sentry_scope_t *scope) { - return scope->contexts; + return scope->data->contexts; } void sentry_scope_set_context( sentry_scope_t *scope, const char *key, sentry_value_t value) { - if (sentry_value_set_by_key(scope->contexts, key, value) == 0) { + if (data_set_by_key(scope->data, &scope->data->contexts, key, + sentry_value_incref(value))) { SENTRY_SCOPE_NOTIFY(scope, set_context, key, value); } + sentry_value_decref(value); } void sentry_scope_set_context_n(sentry_scope_t *scope, const char *key, size_t key_len, sentry_value_t value) { - char *k = sentry__string_clone_n(key, key_len); - if (sentry__value_set_by_key_owned(scope->contexts, k, key_len, value) - == 0) { - SENTRY_SCOPE_NOTIFY(scope, set_context, k, value); + char *notify_key = sentry__string_clone_n(key, key_len); + if (!notify_key) { + sentry_value_decref(value); + return; } + + if (data_set_by_key_n(scope->data, &scope->data->contexts, key, key_len, + sentry_value_incref(value))) { + SENTRY_SCOPE_NOTIFY(scope, set_context, notify_key, value); + } + sentry_free(notify_key); + sentry_value_decref(value); } void @@ -1142,28 +1312,24 @@ void sentry_scope_update_context_n(sentry_scope_t *scope, const char *key, size_t key_len, sentry_value_t value) { - sentry_value_t context - = sentry_value_get_by_key_n(scope->contexts, key, key_len); - char *k = sentry__string_clone_n(key, key_len); - if (sentry_value_is_null(context)) { - if (sentry__value_set_by_key_owned(scope->contexts, k, key_len, value) - != 0) { - return; - } - } else { - sentry__value_merge_objects(value, context); - if (sentry__value_set_by_key_owned(scope->contexts, k, key_len, value) - != 0) { - return; - } + char *notify_key = sentry__string_clone_n(key, key_len); + if (!notify_key) { + sentry_value_decref(value); + return; } - SENTRY_SCOPE_NOTIFY(scope, set_context, k, value); + + if (data_update_context_by_key_n( + scope->data, key, key_len, sentry_value_incref(value))) { + SENTRY_SCOPE_NOTIFY(scope, set_context, notify_key, value); + } + sentry_free(notify_key); + sentry_value_decref(value); } void sentry__scope_remove_context(sentry_scope_t *scope, const char *key) { - if (sentry_value_remove_by_key(scope->contexts, key) == 0) { + if (data_remove_by_key(scope->data, &scope->data->contexts, key)) { SENTRY_SCOPE_NOTIFY(scope, remove_context, key); } } @@ -1172,8 +1338,8 @@ void sentry__scope_remove_context_n( sentry_scope_t *scope, const char *key, size_t key_len) { - char *k - = sentry__value_remove_and_take_key_n(scope->contexts, key, key_len); + char *k = data_remove_and_take_key_n( + scope->data, &scope->data->contexts, key, key_len); if (k) { SENTRY_SCOPE_NOTIFY(scope, remove_context, k); } @@ -1186,7 +1352,7 @@ sentry__scope_ref_fingerprint(const sentry_scope_t *scope) sentry_value_t fingerprint = sentry_value_new_null(); SCOPE_READ_LOCK(scope) { - fingerprint = sentry_value_incref(scope->fingerprint); + fingerprint = sentry_value_incref(scope->data->fingerprint); } return fingerprint; } @@ -1257,7 +1423,7 @@ sentry_scope_set_fingerprints( SCOPE_WRITE_LOCK(scope) { sentry__value_replace( - &scope->fingerprint, sentry_value_incref(fingerprints)); + &scope->data->fingerprint, sentry_value_incref(fingerprints)); } SENTRY_SCOPE_NOTIFY_OWNED(scope, set_fingerprint, fingerprints); } @@ -1269,7 +1435,7 @@ sentry_scope_remove_fingerprint(sentry_scope_t *scope) SCOPE_WRITE_LOCK(scope) { sentry__value_replace( - &scope->fingerprint, sentry_value_incref(fingerprint)); + &scope->data->fingerprint, sentry_value_incref(fingerprint)); } SENTRY_SCOPE_NOTIFY_OWNED(scope, set_fingerprint, fingerprint); } @@ -1278,14 +1444,20 @@ sentry_level_t sentry__scope_get_level(const sentry_scope_t *scope) { sentry_level_t level = SENTRY_LEVEL_ERROR; - SCOPE_READ_LOCK(scope) { level = scope->level; } + SCOPE_READ_LOCK(scope) { level = scope->data->level; } return level; } +sentry_value_t +sentry__scope_get_client_sdk(const sentry_scope_t *scope) +{ + return scope->data->client_sdk; +} + void sentry_scope_set_level(sentry_scope_t *scope, sentry_level_t level) { - SCOPE_WRITE_LOCK(scope) { scope->level = level; } + SCOPE_WRITE_LOCK(scope) { scope->data->level = level; } SENTRY_SCOPE_NOTIFY(scope, set_level, level); } @@ -1312,19 +1484,41 @@ sentry_scope_set_span(sentry_scope_t *scope, sentry_span_t *span) scope->span = span; } +sentry_value_t +sentry__scope_get_attachments(const sentry_scope_t *scope) +{ + return scope->data->attachments; +} + sentry_value_t sentry__scope_add_attachment(sentry_scope_t *scope, sentry_value_t attachment) { - size_t len = sentry_value_get_length(scope->attachments); + size_t len = sentry_value_get_length(scope->data->attachments); sentry_value_t added - = sentry__attachments_add(&scope->attachments, attachment); + = sentry__attachments_add(&scope->data->attachments, attachment); if (!sentry_value_is_null(added) - && sentry_value_get_length(scope->attachments) > len) { + && sentry_value_get_length(scope->data->attachments) > len) { SENTRY_SCOPE_NOTIFY(scope, add_attachment, added); } return added; } +sentry_value_t +sentry__scope_take_attachments(sentry_scope_t *scope) +{ + sentry_value_t attachments = scope->data->attachments; + scope->data->attachments = sentry__attachments_new(); + return attachments; +} + +sentry_value_t +sentry__scope_remove_attachment( + sentry_scope_t *scope, const sentry_uuid_t *attachment_id) +{ + return sentry__attachments_remove( + scope->data->attachments, attachment_id); +} + sentry_uuid_t sentry_scope_add_attachment(sentry_scope_t *scope, sentry_value_t attachment) { @@ -1342,7 +1536,7 @@ sentry_scope_add_attachment(sentry_scope_t *scope, sentry_value_t attachment) sentry_transaction_t * sentry__scope_get_transaction_object(const sentry_scope_t *scope) { - return scope->transaction_object; + return scope->data->transaction_object; } static bool @@ -1358,21 +1552,21 @@ sentry__scope_set_transaction_object( sentry_scope_t *scope, sentry_transaction_t *transaction) { sentry__transaction_incref(transaction); - sentry__span_decref(scope->span); - scope->span = NULL; - sentry__transaction_decref(scope->transaction_object); - scope->transaction_object = transaction; + sentry__span_decref(scope->data->span); + scope->data->span = NULL; + sentry__transaction_decref(scope->data->transaction_object); + scope->data->transaction_object = transaction; } bool sentry__scope_remove_transaction_object( sentry_scope_t *scope, sentry_transaction_t *transaction) { - if (!transaction || scope->transaction_object != transaction) { + if (!transaction || scope->data->transaction_object != transaction) { return false; } - scope->transaction_object = NULL; + scope->data->transaction_object = NULL; sentry__transaction_decref(transaction); return true; } @@ -1383,13 +1577,14 @@ sentry__scope_remove_transaction_value( { const char *span_id = sentry_value_as_string( sentry_value_get_by_key(transaction, "span_id")); - if (!scope->transaction_object - || !value_has_span_id(scope->transaction_object->inner, span_id)) { + if (!scope->data->transaction_object + || !value_has_span_id( + scope->data->transaction_object->inner, span_id)) { return false; } - sentry_transaction_t *transaction_object = scope->transaction_object; - scope->transaction_object = NULL; + sentry_transaction_t *transaction_object = scope->data->transaction_object; + scope->data->transaction_object = NULL; sentry__transaction_decref(transaction_object); return true; } @@ -1398,38 +1593,38 @@ bool sentry__scope_restore_transaction_object( sentry_scope_t *scope, sentry_transaction_t *transaction) { - if (scope->transaction_object || !transaction) { + if (scope->data->transaction_object || !transaction) { return false; } - scope->transaction_object = transaction; + scope->data->transaction_object = transaction; return true; } sentry_span_t * sentry__scope_get_span(const sentry_scope_t *scope) { - return scope->span; + return scope->data->span; } void sentry__scope_set_span(sentry_scope_t *scope, sentry_span_t *span) { sentry__span_incref(span); - sentry__transaction_decref(scope->transaction_object); - scope->transaction_object = NULL; - sentry__span_decref(scope->span); - scope->span = span; + sentry__transaction_decref(scope->data->transaction_object); + scope->data->transaction_object = NULL; + sentry__span_decref(scope->data->span); + scope->data->span = span; } bool sentry__scope_remove_span(sentry_scope_t *scope, sentry_span_t *span) { - if (!span || scope->span != span) { + if (!span || scope->data->span != span) { return false; } - scope->span = NULL; + scope->data->span = NULL; sentry__span_decref(span); return true; } @@ -1439,12 +1634,13 @@ sentry__scope_remove_span_value(sentry_scope_t *scope, sentry_value_t span) { const char *span_id = sentry_value_as_string(sentry_value_get_by_key(span, "span_id")); - if (!scope->span || !value_has_span_id(scope->span->inner, span_id)) { + if (!scope->data->span + || !value_has_span_id(scope->data->span->inner, span_id)) { return false; } - sentry_span_t *scope_span = scope->span; - scope->span = NULL; + sentry_span_t *scope_span = scope->data->span; + scope->data->span = NULL; sentry__span_decref(scope_span); return true; } @@ -1452,11 +1648,11 @@ sentry__scope_remove_span_value(sentry_scope_t *scope, sentry_value_t span) bool sentry__scope_restore_span(sentry_scope_t *scope, sentry_span_t *span) { - if (scope->span || !span) { + if (scope->data->span || !span) { return false; } - scope->span = span; + scope->data->span = span; return true; } @@ -1464,7 +1660,10 @@ sentry_value_t sentry__scope_ref_release(const sentry_scope_t *scope) { sentry_value_t release = sentry_value_new_null(); - SCOPE_READ_LOCK(scope) { release = sentry_value_incref(scope->release); } + SCOPE_READ_LOCK(scope) + { + release = sentry_value_incref(scope->data->release); + } return release; } @@ -1475,9 +1674,10 @@ sentry__scope_set_release_n( sentry_value_t value = sentry_value_new_string_n(release, release_len); SCOPE_WRITE_LOCK(scope) { - sentry__value_replace(&scope->release, sentry_value_incref(value)); - sentry_value_set_by_key(scope->dynamic_sampling_context, "release", - sentry_value_incref(value)); + sentry__value_replace( + &scope->data->release, sentry_value_incref(value)); + sentry_value_set_by_key(scope->data->dynamic_sampling_context, + "release", sentry_value_incref(value)); } SENTRY_SCOPE_NOTIFY_OWNED(scope, set_release, value); } @@ -1488,7 +1688,7 @@ sentry__scope_ref_environment(const sentry_scope_t *scope) sentry_value_t environment = sentry_value_new_null(); SCOPE_READ_LOCK(scope) { - environment = sentry_value_incref(scope->environment); + environment = sentry_value_incref(scope->data->environment); } return environment; } @@ -1501,9 +1701,10 @@ sentry__scope_set_environment_n( = sentry_value_new_string_n(environment, environment_len); SCOPE_WRITE_LOCK(scope) { - sentry__value_replace(&scope->environment, sentry_value_incref(value)); - sentry_value_set_by_key(scope->dynamic_sampling_context, "environment", - sentry_value_incref(value)); + sentry__value_replace( + &scope->data->environment, sentry_value_incref(value)); + sentry_value_set_by_key(scope->data->dynamic_sampling_context, + "environment", sentry_value_incref(value)); } SENTRY_SCOPE_NOTIFY_OWNED(scope, set_environment, value); } @@ -1514,7 +1715,7 @@ sentry__scope_ref_transaction(const sentry_scope_t *scope) sentry_value_t transaction = sentry_value_new_null(); SCOPE_READ_LOCK(scope) { - transaction = sentry_value_incref(scope->transaction); + transaction = sentry_value_incref(scope->data->transaction); } return transaction; } @@ -1527,10 +1728,11 @@ sentry__scope_set_transaction_n( = sentry_value_new_string_n(transaction, transaction_len); SCOPE_WRITE_LOCK(scope) { - sentry__value_replace(&scope->transaction, sentry_value_incref(value)); - if (scope->transaction_object) { + sentry__value_replace( + &scope->data->transaction, sentry_value_incref(value)); + if (scope->data->transaction_object) { sentry_transaction_set_name_n( - scope->transaction_object, transaction, transaction_len); + scope->data->transaction_object, transaction, transaction_len); } } SENTRY_SCOPE_NOTIFY_OWNED(scope, set_transaction, value); @@ -1605,31 +1807,32 @@ void sentry__scope_apply_to_telemetry(const sentry_scope_t *scope, sentry_value_t telemetry, sentry_value_t attributes) { - sentry__value_merge_objects_shallow(attributes, scope->attributes); + sentry__value_merge_objects_shallow(attributes, scope->data->attributes); // a span on the scope MUST take precedence over the propagation context sentry_value_t trace_id = sentry_value_get_by_key( - sentry_value_get_by_key(scope->propagation_context, "trace"), + sentry_value_get_by_key(scope->data->propagation_context, "trace"), "trace_id"); sentry_value_t parent_span_id = sentry_value_new_object(); - if (scope->transaction_object) { + if (scope->data->transaction_object) { sentry_value_t span_id = sentry_value_get_by_key( - scope->transaction_object->inner, "span_id"); + scope->data->transaction_object->inner, "span_id"); sentry_value_incref(span_id); sentry_value_set_by_key(parent_span_id, "value", span_id); trace_id = sentry_value_get_by_key( - scope->transaction_object->inner, "trace_id"); - } else if (scope->span) { + scope->data->transaction_object->inner, "trace_id"); + } else if (scope->data->span) { sentry_value_t span_id - = sentry_value_get_by_key(scope->span->inner, "span_id"); + = sentry_value_get_by_key(scope->data->span->inner, "span_id"); sentry_value_incref(span_id); sentry_value_set_by_key(parent_span_id, "value", span_id); - trace_id = sentry_value_get_by_key(scope->span->inner, "trace_id"); + trace_id + = sentry_value_get_by_key(scope->data->span->inner, "trace_id"); } sentry_value_set_by_key( parent_span_id, "type", sentry_value_new_string("string")); - if ((scope->transaction_object || scope->span) + if ((scope->data->transaction_object || scope->data->span) && sentry_value_is_null(sentry_value_get_by_key( attributes, "sentry.trace.parent_span_id"))) { sentry_value_set_by_key( @@ -1670,22 +1873,30 @@ sentry__scope_apply_to_telemetry(const sentry_scope_t *scope, } sentry_value_decref(user); - sentry_value_t os_context = sentry_value_get_by_key(scope->contexts, "os"); - if (!sentry_value_is_null(os_context)) { - sentry_value_t os_name = sentry_value_get_by_key(os_context, "name"); - sentry_value_t os_version - = sentry_value_get_by_key(os_context, "version"); - if (!sentry_value_is_null(os_name)) { - sentry_value_incref(os_name); - sentry__value_add_attribute( - attributes, os_name, "string", "os.name"); - } - if (!sentry_value_is_null(os_version)) { - sentry_value_incref(os_version); - sentry__value_add_attribute( - attributes, os_version, "string", "os.version"); + sentry_value_t os_name = sentry_value_new_null(); + sentry_value_t os_version = sentry_value_new_null(); + SCOPE_READ_LOCK(scope) + { + sentry_value_t os_context + = sentry_value_get_by_key(scope->data->contexts, "os"); + if (!sentry_value_is_null(os_context)) { + os_name = sentry_value_incref( + sentry_value_get_by_key(os_context, "name")); + os_version = sentry_value_incref( + sentry_value_get_by_key(os_context, "version")); } } + if (!sentry_value_is_null(os_name)) { + sentry__value_add_attribute(attributes, os_name, "string", "os.name"); + } else { + sentry_value_decref(os_name); + } + if (!sentry_value_is_null(os_version)) { + sentry__value_add_attribute( + attributes, os_version, "string", "os.version"); + } else { + sentry_value_decref(os_version); + } sentry_value_t environment = sentry__scope_ref_environment(scope); if (!sentry_value_is_null(environment)) { sentry__value_add_attribute(attributes, @@ -1705,7 +1916,9 @@ sentry_uuid_t sentry_scope_get_last_event_id(const sentry_scope_t *scope) { sentry_uuid_t event_id = sentry_uuid_nil(); - SCOPE_READ_LOCK(scope) { event_id = scope->last_event_id; } + if (scope) { + SCOPE_READ_LOCK(scope) { event_id = scope->data->last_event_id; } + } return event_id; } @@ -1713,7 +1926,9 @@ void sentry__scope_set_last_event_id( sentry_scope_t *scope, sentry_uuid_t event_id) { - SCOPE_WRITE_LOCK(scope) { scope->last_event_id = event_id; } + if (scope) { + SCOPE_WRITE_LOCK(scope) { scope->data->last_event_id = event_id; } + } } void diff --git a/src/sentry_scope.h b/src/sentry_scope.h index ba56cad57a..e98e213a31 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -50,40 +50,13 @@ typedef struct sentry_scope_observer_s { void (*remove_attachment)(void *data, sentry_value_t attachment); } sentry_scope_observer_t; +typedef struct sentry_scope_data_s sentry_scope_data_t; + /** * This represents the current scope. */ struct sentry_scope_s { - sentry_rwlock_t rwlock; - - sentry_value_t release; - sentry_value_t environment; - sentry_value_t transaction; - sentry_value_t fingerprint; - sentry_value_t user; - sentry_value_t tags; - sentry_value_t extra; - sentry_value_t attributes; - sentry_value_t contexts; - sentry_value_t propagation_context; - sentry_ringbuffer_t *breadcrumbs; - sentry_value_t dynamic_sampling_context; - sentry_level_t level; - sentry_uuid_t last_event_id; - sentry_value_t client_sdk; - sentry_value_t attachments; - - // The span attached to this scope, if any. - // - // Conceptually, every transaction is a span, so it should be possible to - // attach spans or transactions to a scope. But sentry_span_t and - // sentry_transaction_t are unrelated types in the native SDK, so we need - // two distinct pointers. At most one of them should ever be non-null. - // Whenever possible, `transaction` should pull its value from the - // `name` property nested in transaction_object or span. - sentry_transaction_t *transaction_object; - sentry_span_t *span; - bool trace_managed; + sentry_scope_data_t *data; sentry_scope_observer_t **observers; size_t num_observers; @@ -173,9 +146,14 @@ void sentry__scope_set_fingerprint_nva(sentry_scope_t *scope, const char *fingerprint, size_t fingerprint_len, va_list va); sentry_value_t sentry__scope_ref_user(const sentry_scope_t *scope); sentry_level_t sentry__scope_get_level(const sentry_scope_t *scope); +sentry_value_t sentry__scope_get_client_sdk(const sentry_scope_t *scope); +sentry_value_t sentry__scope_get_attachments(const sentry_scope_t *scope); sentry_value_t sentry__scope_add_attachment( sentry_scope_t *scope, sentry_value_t attachment); +sentry_value_t sentry__scope_take_attachments(sentry_scope_t *scope); +sentry_value_t sentry__scope_remove_attachment( + sentry_scope_t *scope, const sentry_uuid_t *attachment_id); sentry_value_t sentry__scope_get_tags(const sentry_scope_t *scope); void sentry__scope_remove_tag(sentry_scope_t *scope, const char *key); void sentry__scope_remove_tag_n( diff --git a/src/sentry_tracing.h b/src/sentry_tracing.h index 5ba423d170..2c9907794a 100644 --- a/src/sentry_tracing.h +++ b/src/sentry_tracing.h @@ -59,8 +59,8 @@ void sentry__transaction_remove_child( /** * Finishes the active transaction (if any) with `status`, closing out every * in-flight child span in leaf-first order and returning the tx payload. - * `scope->span` / `scope->transaction_object` are preserved so a - * subsequently-captured crash event still inherits the active trace context. + * The scope span / transaction object are preserved so a subsequently-captured + * crash event still inherits the active trace context. * Returns null if nothing is active. */ sentry_value_t sentry__trace_finish(sentry_span_status_t status); diff --git a/tests/unit/test_attachments.c b/tests/unit/test_attachments.c index 066e8de8a2..73d50bcbee 100644 --- a/tests/unit/test_attachments.c +++ b/tests/unit/test_attachments.c @@ -17,7 +17,8 @@ static void add_scope_attachments(sentry_envelope_t *envelope) { SENTRY_WITH_SCOPE (scope) { - sentry__envelope_add_attachments(envelope, scope->attachments, NULL); + sentry__envelope_add_attachments( + envelope, sentry__scope_get_attachments(scope), NULL); } } diff --git a/tests/unit/test_basic.c b/tests/unit/test_basic.c index b08f2551ab..9e4a06667a 100644 --- a/tests/unit/test_basic.c +++ b/tests/unit/test_basic.c @@ -425,7 +425,8 @@ SENTRY_TEST(client_sdk_integrations) SENTRY_WITH_SCOPE (scope) { sentry_value_t integrations - = sentry_value_get_by_key(scope->client_sdk, "integrations"); + = sentry_value_get_by_key( + sentry__scope_get_client_sdk(scope), "integrations"); size_t integration_count = sentry_value_get_length(integrations); TEST_CHECK(integration_count > 0); TEST_CHECK_STRING_EQUAL( diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 617e007559..a39a9146b9 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -2566,7 +2566,7 @@ SENTRY_TEST(scope_clone_preserves_data) sentry_scope_t *clone = sentry_scope_clone(scope); sentry_value_decref( - sentry__attachments_remove(scope->attachments, &attachment_id)); + sentry__scope_remove_attachment(scope, &attachment_id)); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( sentry__scope_get_tags(clone), "tag_key")), @@ -2595,11 +2595,13 @@ SENTRY_TEST(scope_clone_preserves_data) TEST_CHECK_INT_EQUAL(scope_breadcrumb_count(clone), 1); // Attachments are deep-copied into an independent list. - TEST_CHECK_INT_EQUAL(sentry_value_get_length(clone->attachments), 1); - TEST_CHECK_INT_EQUAL(sentry_value_get_length(scope->attachments), 0); - TEST_CHECK(clone->attachments._bits != scope->attachments._bits); + sentry_value_t clone_attachments = sentry__scope_get_attachments(clone); + sentry_value_t scope_attachments = sentry__scope_get_attachments(scope); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(clone_attachments), 1); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(scope_attachments), 0); + TEST_CHECK(clone_attachments._bits != scope_attachments._bits); sentry_value_t clone_attachment - = sentry_value_get_by_index(clone->attachments, 0); + = sentry_value_get_by_index(clone_attachments, 0); TEST_CHECK_STRING_EQUAL( sentry__attachment_get_filename(clone_attachment), "file.bin"); TEST_CHECK_INT_EQUAL(sentry__attachment_get_size(clone_attachment), 7); From 3b61edffb08910043a9f13058736e747ab48417d Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 6 Aug 2026 14:35:59 +0200 Subject: [PATCH 09/22] refactor(scope): hide direct scope data access Move scope payload access behind sentry_scope_data_t helpers so scope-level code no longer reaches through scope->data to individual fields. --- src/sentry_scope.c | 1522 +++++++++++++++++++++++++++------------ src/sentry_scope.h | 2 +- tests/unit/test_basic.c | 5 +- 3 files changed, 1048 insertions(+), 481 deletions(-) diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 6ee7480d9d..c277367cd2 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -71,9 +71,8 @@ struct sentry_scope_data_s { static sentry_scope_t g_scope = { 0 }; static sentry_scope_data_t g_scope_data = { 0 }; -#define SCOPE_READ_LOCK(Scope) \ - for (const sentry_scope_data_t *_locked_data = (Scope)->data; \ - _locked_data; \ +#define DATA_READ_LOCK(Data) \ + for (const sentry_scope_data_t *_locked_data = (Data); _locked_data; \ sentry__rwlock_read_unlock((sentry_rwlock_t *)&_locked_data->rwlock), \ _locked_data = NULL) \ for (bool _locked_once \ @@ -82,8 +81,8 @@ static sentry_scope_data_t g_scope_data = { 0 }; true); \ _locked_once; _locked_once = false) -#define SCOPE_WRITE_LOCK(Scope) \ - for (sentry_scope_data_t *_locked_data = (Scope)->data; _locked_data; \ +#define DATA_WRITE_LOCK(Data) \ + for (sentry_scope_data_t *_locked_data = (Data); _locked_data; \ sentry__rwlock_write_unlock(&_locked_data->rwlock), \ _locked_data = NULL) \ for (bool _locked_once \ @@ -131,46 +130,6 @@ get_client_sdk(void) return client_sdk; } -static bool -data_set_by_key(sentry_scope_data_t *data, sentry_value_t *object, - const char *key, sentry_value_t value) -{ - sentry__rwlock_write_lock(&data->rwlock); - int rv = sentry_value_set_by_key(*object, key, value); - sentry__rwlock_write_unlock(&data->rwlock); - return rv == 0; -} - -static bool -data_set_by_key_n(sentry_scope_data_t *data, sentry_value_t *object, - const char *key, size_t key_len, sentry_value_t value) -{ - sentry__rwlock_write_lock(&data->rwlock); - int rv = sentry_value_set_by_key_n(*object, key, key_len, value); - sentry__rwlock_write_unlock(&data->rwlock); - return rv == 0; -} - -static bool -data_remove_by_key( - sentry_scope_data_t *data, sentry_value_t *object, const char *key) -{ - sentry__rwlock_write_lock(&data->rwlock); - int rv = sentry_value_remove_by_key(*object, key); - sentry__rwlock_write_unlock(&data->rwlock); - return rv == 0; -} - -static char * -data_remove_and_take_key_n(sentry_scope_data_t *data, sentry_value_t *object, - const char *key, size_t key_len) -{ - sentry__rwlock_write_lock(&data->rwlock); - char *rv = sentry__value_remove_and_take_key_n(*object, key, key_len); - sentry__rwlock_write_unlock(&data->rwlock); - return rv; -} - static bool data_update_context_by_key_n(sentry_scope_data_t *data, const char *key, size_t key_len, sentry_value_t value) @@ -228,46 +187,959 @@ init_data(sentry_scope_data_t *data) } static void -cleanup_data(sentry_scope_data_t *data) +cleanup_data(sentry_scope_data_t *data) +{ + sentry_value_decref(data->release); + sentry_value_decref(data->environment); + sentry_value_decref(data->transaction); + sentry_value_decref(data->fingerprint); + sentry_value_decref(data->user); + sentry_value_decref(data->tags); + sentry_value_decref(data->extra); + sentry_value_decref(data->attributes); + sentry_value_decref(data->contexts); + sentry_value_decref(data->propagation_context); + sentry__ringbuffer_free(data->breadcrumbs); + sentry_value_decref(data->dynamic_sampling_context); + sentry_value_decref(data->client_sdk); + sentry_value_decref(data->attachments); + sentry__transaction_decref(data->transaction_object); + sentry__span_decref(data->span); +} + +static sentry_scope_data_t * +new_data(void) +{ + sentry_scope_data_t *data = SENTRY_MAKE(sentry_scope_data_t); + if (data) { + sentry__rwlock_init(&data->rwlock); + init_data(data); + } + return data; +} + +static void +free_data(sentry_scope_data_t *data) +{ + if (!data) { + return; + } + cleanup_data(data); + sentry__rwlock_free(&data->rwlock); + sentry_free(data); +} + +static void +cleanup_global_data(sentry_scope_data_t *data) +{ + cleanup_data(data); + sentry__rwlock_free(&data->rwlock); +} + +static void +init_global_data(sentry_scope_data_t *data) +{ + sentry__value_replace(&data->user, sentry_value_new_object()); + sentry_value_set_by_key(data->contexts, "os", sentry__get_os_context()); + sentry__value_replace(&data->client_sdk, get_client_sdk()); +} + +static void +data_apply_options(sentry_scope_data_t *data, sentry_options_t *options) +{ + DATA_WRITE_LOCK(data) + { + if (options->sdk_name) { + sentry_value_t sdk_name + = sentry_value_new_string(options->sdk_name); + sentry_value_set_by_key(data->client_sdk, "name", sdk_name); + } + sentry_value_t integrations + = sentry_value_get_by_key(data->client_sdk, "integrations"); + for (size_t i = 0; i < options->num_integrations; i++) { + const char *name = options->integrations[i]->name; + if (!name) { + continue; + } + if (sentry_value_is_null(integrations)) { + integrations = sentry_value_new_list(); + sentry_value_set_by_key( + data->client_sdk, "integrations", integrations); + } + sentry_value_append(integrations, sentry_value_new_string(name)); + } + sentry_value_freeze(data->client_sdk); + generate_propagation_context(data->propagation_context); + sentry_value_decref(data->attachments); + data->attachments = options->attachments; + options->attachments = sentry_value_new_null(); + sentry__ringbuffer_set_max_size( + data->breadcrumbs, options->max_breadcrumbs); + } +} + +static void +clear_data(sentry_scope_data_t *data) +{ + DATA_WRITE_LOCK(data) + { + bool trace_managed = data->trace_managed; + sentry_value_t propagation_context + = sentry_value_incref(data->propagation_context); + sentry_value_t dynamic_sampling_context + = sentry_value_incref(data->dynamic_sampling_context); + + cleanup_data(data); + init_data(data); + + sentry_value_decref(data->propagation_context); + sentry_value_decref(data->dynamic_sampling_context); + data->propagation_context = propagation_context; + data->dynamic_sampling_context = dynamic_sampling_context; + data->trace_managed = trace_managed; + } +} + +static sentry_scope_data_t * +clone_data(const sentry_scope_data_t *source) +{ + sentry_scope_data_t *clone = SENTRY_MAKE(sentry_scope_data_t); + if (!clone) { + return NULL; + } + + sentry__rwlock_init(&clone->rwlock); + DATA_READ_LOCK(source) + { + clone->release = sentry__value_clone(source->release); + clone->environment = sentry__value_clone(source->environment); + clone->transaction = sentry__value_clone(source->transaction); + clone->fingerprint = sentry__value_clone(source->fingerprint); + clone->user = sentry__value_clone(source->user); + clone->tags = sentry__value_clone(source->tags); + clone->extra = sentry__value_clone(source->extra); + clone->attributes = sentry__value_clone(source->attributes); + clone->contexts = sentry__value_clone(source->contexts); + clone->propagation_context + = sentry__value_clone(source->propagation_context); + clone->breadcrumbs = sentry__ringbuffer_clone(source->breadcrumbs); + clone->dynamic_sampling_context + = sentry__value_clone(source->dynamic_sampling_context); + if (sentry_value_is_frozen(source->dynamic_sampling_context)) { + sentry_value_freeze(clone->dynamic_sampling_context); + } + clone->level = source->level; + clone->last_event_id = source->last_event_id; + clone->client_sdk = sentry__value_clone(source->client_sdk); + sentry__attachments_extend(&clone->attachments, source->attachments); + clone->transaction_object = source->transaction_object; + sentry__transaction_incref(clone->transaction_object); + clone->span = source->span; + sentry__span_incref(clone->span); + clone->trace_managed = source->trace_managed; + } + + return clone; +} + +static sentry_value_t +data_get_propagation_context(const sentry_scope_data_t *data) +{ + return data->propagation_context; +} + +static sentry_value_t +data_ref_propagation_context(const sentry_scope_data_t *data) +{ + sentry_value_t propagation_context = sentry_value_new_null(); + DATA_READ_LOCK(data) + { + propagation_context = sentry_value_incref(data->propagation_context); + } + return propagation_context; +} + +static sentry_value_t +data_get_trace_context(const sentry_scope_data_t *data) +{ + return sentry_value_get_by_key(data->propagation_context, "trace"); +} + +static void +data_set_propagation_context( + sentry_scope_data_t *data, const char *key, sentry_value_t value) +{ + DATA_WRITE_LOCK(data) + { + sentry_value_set_by_key(data->propagation_context, key, value); + } +} + +static void +data_regenerate_propagation_context(sentry_scope_data_t *data) +{ + DATA_WRITE_LOCK(data) + { + generate_propagation_context(data->propagation_context); + } +} + +static bool +data_is_trace_managed(const sentry_scope_data_t *data) +{ + bool managed = false; + DATA_READ_LOCK(data) { managed = data->trace_managed; } + return managed; +} + +static void +data_set_trace_managed(sentry_scope_data_t *data, bool managed) +{ + DATA_WRITE_LOCK(data) { data->trace_managed = managed; } +} + +static sentry_value_t +data_get_dsc(const sentry_scope_data_t *data) +{ + return data->dynamic_sampling_context; +} + +static void +data_freeze_dsc(sentry_scope_data_t *data, sentry_value_t incoming) +{ + sentry_value_t dsc = sentry_value_new_object(); + sentry__value_merge_objects(dsc, incoming); + sentry_value_freeze(dsc); + DATA_WRITE_LOCK(data) + { + sentry__value_replace(&data->dynamic_sampling_context, dsc); + } +} + +static void +data_update_dsc(sentry_scope_data_t *data, const sentry_options_t *options) +{ + sentry_value_t dsc = sentry_value_new_object(); + + if (options->dsn) { + sentry_value_set_by_key(dsc, "public_key", + sentry_value_new_string(options->dsn->public_key)); + } + const char *org_id = sentry__options_get_org_id(options); + if (org_id) { + sentry_value_set_by_key(dsc, "org_id", sentry_value_new_string(org_id)); + } + sentry_value_set_by_key(dsc, "sample_rate", + sentry_value_new_double(options->traces_sample_rate)); + if (options->traces_sampler) { + sentry_value_set_by_key( + dsc, "sample_rate", sentry_value_new_double(1.0)); + } + + DATA_WRITE_LOCK(data) + { + sentry_value_t sample_rand = sentry_value_get_by_key( + sentry_value_get_by_key(data->propagation_context, "trace"), + "sample_rand"); + sentry_value_set_by_key( + dsc, "sample_rand", sentry_value_incref(sample_rand)); + sentry_value_set_by_key( + dsc, "release", sentry_value_incref(data->release)); + sentry_value_set_by_key( + dsc, "environment", sentry_value_incref(data->environment)); + sentry__value_replace(&data->dynamic_sampling_context, dsc); + } +} + +static sentry_value_t +data_ref_client_sdk(const sentry_scope_data_t *data) +{ + sentry_value_t client_sdk = sentry_value_new_null(); + DATA_READ_LOCK(data) { client_sdk = sentry_value_incref(data->client_sdk); } + return client_sdk; +} + +static void +data_apply_tags_and_extra(const sentry_scope_data_t *data, sentry_value_t event) +{ + DATA_READ_LOCK(data) + { + sentry_value_t event_tags = sentry_value_get_by_key(event, "tags"); + if (sentry_value_is_null(event_tags)) { + if (!sentry_value_is_null(data->tags)) { + sentry_value_set_by_key( + event, "tags", sentry__value_clone(data->tags)); + } + } else { + sentry__value_merge_objects(event_tags, data->tags); + } + + sentry_value_t event_extra = sentry_value_get_by_key(event, "extra"); + if (sentry_value_is_null(event_extra)) { + if (!sentry_value_is_null(data->extra)) { + sentry_value_set_by_key( + event, "extra", sentry__value_clone(data->extra)); + } + } else { + sentry__value_merge_objects(event_extra, data->extra); + } + } +} + +static sentry_value_t +data_clone_contexts(const sentry_scope_data_t *data) +{ + sentry_value_t contexts = sentry_value_new_null(); + DATA_READ_LOCK(data) { contexts = sentry__value_clone(data->contexts); } + return contexts; +} + +static sentry_value_t +data_ref_span_or_transaction(const sentry_scope_data_t *data) +{ + sentry_value_t value = sentry_value_new_null(); + DATA_READ_LOCK(data) + { + if (data->span) { + value = sentry_value_incref(data->span->inner); + } else if (data->transaction_object) { + value = sentry_value_incref(data->transaction_object->inner); + } + } + return value; +} + +static sentry_value_t +data_breadcrumbs_to_list(const sentry_scope_data_t *data) +{ + sentry_value_t breadcrumbs = sentry_value_new_null(); + DATA_READ_LOCK(data) + { + breadcrumbs = sentry__ringbuffer_to_list(data->breadcrumbs); + } + return breadcrumbs; +} + +static bool +data_add_breadcrumb(sentry_scope_data_t *data, sentry_value_t breadcrumb) +{ + bool added = false; + DATA_WRITE_LOCK(data) + { + added = sentry__ringbuffer_append(data->breadcrumbs, breadcrumb) == 0; + } + return added; +} + +static const sentry_ringbuffer_t * +data_get_breadcrumbs(const sentry_scope_data_t *data) +{ + return data->breadcrumbs; +} + +static sentry_value_t +data_ref_user(const sentry_scope_data_t *data) +{ + sentry_value_t user = sentry_value_new_null(); + DATA_READ_LOCK(data) { user = sentry_value_incref(data->user); } + return user; +} + +static void +data_set_user(sentry_scope_data_t *data, sentry_value_t user) +{ + DATA_WRITE_LOCK(data) + { + sentry__value_replace(&data->user, sentry_value_incref(user)); + } +} + +static sentry_value_t +data_get_tags(const sentry_scope_data_t *data) +{ + return data->tags; +} + +static bool +data_set_tag(sentry_scope_data_t *data, const char *key, sentry_value_t value) +{ + bool did_set = false; + DATA_WRITE_LOCK(data) + { + did_set = sentry_value_set_by_key(data->tags, key, value) == 0; + } + return did_set; +} + +static bool +data_set_tag_n(sentry_scope_data_t *data, const char *key, size_t key_len, + sentry_value_t value) +{ + bool did_set = false; + DATA_WRITE_LOCK(data) + { + did_set + = sentry_value_set_by_key_n(data->tags, key, key_len, value) == 0; + } + return did_set; +} + +static bool +data_remove_tag(sentry_scope_data_t *data, const char *key) +{ + bool removed = false; + DATA_WRITE_LOCK(data) + { + removed = sentry_value_remove_by_key(data->tags, key) == 0; + } + return removed; +} + +static char * +data_remove_tag_n(sentry_scope_data_t *data, const char *key, size_t key_len) +{ + char *removed_key = NULL; + DATA_WRITE_LOCK(data) + { + removed_key + = sentry__value_remove_and_take_key_n(data->tags, key, key_len); + } + return removed_key; +} + +static sentry_value_t +data_get_extra(const sentry_scope_data_t *data) +{ + return data->extra; +} + +static bool +data_set_extra(sentry_scope_data_t *data, const char *key, sentry_value_t value) +{ + bool did_set = false; + DATA_WRITE_LOCK(data) + { + did_set = sentry_value_set_by_key(data->extra, key, value) == 0; + } + return did_set; +} + +static bool +data_set_extra_n(sentry_scope_data_t *data, const char *key, size_t key_len, + sentry_value_t value) +{ + bool did_set = false; + DATA_WRITE_LOCK(data) + { + did_set + = sentry_value_set_by_key_n(data->extra, key, key_len, value) == 0; + } + return did_set; +} + +static bool +data_remove_extra(sentry_scope_data_t *data, const char *key) +{ + bool removed = false; + DATA_WRITE_LOCK(data) + { + removed = sentry_value_remove_by_key(data->extra, key) == 0; + } + return removed; +} + +static char * +data_remove_extra_n(sentry_scope_data_t *data, const char *key, size_t key_len) +{ + char *removed_key = NULL; + DATA_WRITE_LOCK(data) + { + removed_key + = sentry__value_remove_and_take_key_n(data->extra, key, key_len); + } + return removed_key; +} + +static sentry_value_t +data_get_attributes(const sentry_scope_data_t *data) +{ + return data->attributes; +} + +static bool +data_set_attribute_n(sentry_scope_data_t *data, const char *key, size_t key_len, + sentry_value_t attribute) +{ + bool did_set = false; + DATA_WRITE_LOCK(data) + { + did_set = sentry_value_set_by_key_n( + data->attributes, key, key_len, attribute) + == 0; + } + return did_set; +} + +static void +data_remove_attribute(sentry_scope_data_t *data, const char *key) +{ + DATA_WRITE_LOCK(data) { sentry_value_remove_by_key(data->attributes, key); } +} + +static void +data_remove_attribute_n( + sentry_scope_data_t *data, const char *key, size_t key_len) +{ + DATA_WRITE_LOCK(data) + { + sentry_value_remove_by_key_n(data->attributes, key, key_len); + } +} + +static sentry_value_t +data_get_contexts(const sentry_scope_data_t *data) +{ + return data->contexts; +} + +static bool +data_set_context( + sentry_scope_data_t *data, const char *key, sentry_value_t value) +{ + bool did_set = false; + DATA_WRITE_LOCK(data) + { + did_set = sentry_value_set_by_key(data->contexts, key, value) == 0; + } + return did_set; +} + +static bool +data_set_context_n(sentry_scope_data_t *data, const char *key, size_t key_len, + sentry_value_t value) +{ + bool did_set = false; + DATA_WRITE_LOCK(data) + { + did_set = sentry_value_set_by_key_n(data->contexts, key, key_len, value) + == 0; + } + return did_set; +} + +static bool +data_update_context_n(sentry_scope_data_t *data, const char *key, + size_t key_len, sentry_value_t value) +{ + return data_update_context_by_key_n(data, key, key_len, value); +} + +static bool +data_remove_context(sentry_scope_data_t *data, const char *key) +{ + bool removed = false; + DATA_WRITE_LOCK(data) + { + removed = sentry_value_remove_by_key(data->contexts, key) == 0; + } + return removed; +} + +static char * +data_remove_context_n( + sentry_scope_data_t *data, const char *key, size_t key_len) +{ + char *removed_key = NULL; + DATA_WRITE_LOCK(data) + { + removed_key + = sentry__value_remove_and_take_key_n(data->contexts, key, key_len); + } + return removed_key; +} + +static sentry_value_t +data_ref_fingerprint(const sentry_scope_data_t *data) +{ + sentry_value_t fingerprint = sentry_value_new_null(); + DATA_READ_LOCK(data) + { + fingerprint = sentry_value_incref(data->fingerprint); + } + return fingerprint; +} + +static void +data_set_fingerprint(sentry_scope_data_t *data, sentry_value_t fingerprint) +{ + DATA_WRITE_LOCK(data) + { + sentry__value_replace( + &data->fingerprint, sentry_value_incref(fingerprint)); + } +} + +static sentry_level_t +data_get_level(const sentry_scope_data_t *data) +{ + sentry_level_t level = SENTRY_LEVEL_ERROR; + DATA_READ_LOCK(data) { level = data->level; } + return level; +} + +static void +data_set_level(sentry_scope_data_t *data, sentry_level_t level) +{ + DATA_WRITE_LOCK(data) { data->level = level; } +} + +static sentry_uuid_t +data_get_last_event_id(const sentry_scope_data_t *data) +{ + sentry_uuid_t event_id = sentry_uuid_nil(); + DATA_READ_LOCK(data) { event_id = data->last_event_id; } + return event_id; +} + +static void +data_set_last_event_id(sentry_scope_data_t *data, sentry_uuid_t event_id) +{ + DATA_WRITE_LOCK(data) { data->last_event_id = event_id; } +} + +static sentry_value_t +data_get_attachments(const sentry_scope_data_t *data) +{ + return data->attachments; +} + +static sentry_value_t +data_add_attachment(sentry_scope_data_t *data, sentry_value_t attachment, + bool *did_add) +{ + sentry_value_t added = sentry_value_new_null(); + DATA_WRITE_LOCK(data) + { + size_t len = sentry_value_get_length(data->attachments); + added = sentry__attachments_add(&data->attachments, attachment); + *did_add = !sentry_value_is_null(added) + && sentry_value_get_length(data->attachments) > len; + } + return added; +} + +static sentry_value_t +data_remove_attachment( + sentry_scope_data_t *data, const sentry_uuid_t *attachment_id) +{ + sentry_value_t removed = sentry_value_new_null(); + DATA_WRITE_LOCK(data) + { + removed = sentry__attachments_remove(data->attachments, attachment_id); + } + return removed; +} + +static sentry_value_t +data_take_attachments(sentry_scope_data_t *data) +{ + sentry_value_t attachments = sentry_value_new_null(); + DATA_WRITE_LOCK(data) + { + attachments = data->attachments; + data->attachments = sentry__attachments_new(); + } + return attachments; +} + +static sentry_transaction_t * +data_get_transaction_object(const sentry_scope_data_t *data) +{ + sentry_transaction_t *transaction = NULL; + DATA_READ_LOCK(data) { transaction = data->transaction_object; } + return transaction; +} + +static bool +value_has_span_id(sentry_value_t value, const char *span_id) +{ + const char *value_span_id + = sentry_value_as_string(sentry_value_get_by_key(value, "span_id")); + return sentry__string_eq(value_span_id, span_id); +} + +static void +data_set_transaction_object( + sentry_scope_data_t *data, sentry_transaction_t *transaction) +{ + sentry__transaction_incref(transaction); + DATA_WRITE_LOCK(data) + { + sentry__span_decref(data->span); + data->span = NULL; + sentry__transaction_decref(data->transaction_object); + data->transaction_object = transaction; + } +} + +static bool +data_remove_transaction_object( + sentry_scope_data_t *data, sentry_transaction_t *transaction) +{ + bool removed = false; + DATA_WRITE_LOCK(data) + { + if (transaction && data->transaction_object == transaction) { + data->transaction_object = NULL; + removed = true; + } + } + if (removed) { + sentry__transaction_decref(transaction); + } + return removed; +} + +static bool +data_remove_transaction_value( + sentry_scope_data_t *data, sentry_value_t transaction) +{ + const char *span_id = sentry_value_as_string( + sentry_value_get_by_key(transaction, "span_id")); + sentry_transaction_t *transaction_object = NULL; + DATA_WRITE_LOCK(data) + { + if (data->transaction_object + && value_has_span_id(data->transaction_object->inner, span_id)) { + transaction_object = data->transaction_object; + data->transaction_object = NULL; + } + } + sentry__transaction_decref(transaction_object); + return transaction_object != NULL; +} + +static bool +data_restore_transaction_object( + sentry_scope_data_t *data, sentry_transaction_t *transaction) +{ + bool restored = false; + DATA_WRITE_LOCK(data) + { + if (!data->transaction_object && transaction) { + data->transaction_object = transaction; + restored = true; + } + } + return restored; +} + +static sentry_span_t * +data_get_span(const sentry_scope_data_t *data) +{ + sentry_span_t *span = NULL; + DATA_READ_LOCK(data) { span = data->span; } + return span; +} + +static void +data_set_span(sentry_scope_data_t *data, sentry_span_t *span) +{ + sentry__span_incref(span); + DATA_WRITE_LOCK(data) + { + sentry__transaction_decref(data->transaction_object); + data->transaction_object = NULL; + sentry__span_decref(data->span); + data->span = span; + } +} + +static bool +data_remove_span(sentry_scope_data_t *data, sentry_span_t *span) +{ + bool removed = false; + DATA_WRITE_LOCK(data) + { + if (span && data->span == span) { + data->span = NULL; + removed = true; + } + } + if (removed) { + sentry__span_decref(span); + } + return removed; +} + +static bool +data_remove_span_value(sentry_scope_data_t *data, sentry_value_t span) +{ + const char *span_id + = sentry_value_as_string(sentry_value_get_by_key(span, "span_id")); + sentry_span_t *scope_span = NULL; + DATA_WRITE_LOCK(data) + { + if (data->span && value_has_span_id(data->span->inner, span_id)) { + scope_span = data->span; + data->span = NULL; + } + } + sentry__span_decref(scope_span); + return scope_span != NULL; +} + +static bool +data_restore_span(sentry_scope_data_t *data, sentry_span_t *span) +{ + bool restored = false; + DATA_WRITE_LOCK(data) + { + if (!data->span && span) { + data->span = span; + restored = true; + } + } + return restored; +} + +static sentry_value_t +data_ref_release(const sentry_scope_data_t *data) +{ + sentry_value_t release = sentry_value_new_null(); + DATA_READ_LOCK(data) { release = sentry_value_incref(data->release); } + return release; +} + +static void +data_set_release(sentry_scope_data_t *data, sentry_value_t value) +{ + DATA_WRITE_LOCK(data) + { + sentry__value_replace(&data->release, sentry_value_incref(value)); + sentry_value_set_by_key(data->dynamic_sampling_context, "release", + sentry_value_incref(value)); + } +} + +static sentry_value_t +data_ref_environment(const sentry_scope_data_t *data) +{ + sentry_value_t environment = sentry_value_new_null(); + DATA_READ_LOCK(data) + { + environment = sentry_value_incref(data->environment); + } + return environment; +} + +static void +data_set_environment(sentry_scope_data_t *data, sentry_value_t value) { - sentry_value_decref(data->release); - sentry_value_decref(data->environment); - sentry_value_decref(data->transaction); - sentry_value_decref(data->fingerprint); - sentry_value_decref(data->user); - sentry_value_decref(data->tags); - sentry_value_decref(data->extra); - sentry_value_decref(data->attributes); - sentry_value_decref(data->contexts); - sentry_value_decref(data->propagation_context); - sentry__ringbuffer_free(data->breadcrumbs); - sentry_value_decref(data->dynamic_sampling_context); - sentry_value_decref(data->client_sdk); - sentry_value_decref(data->attachments); - sentry__transaction_decref(data->transaction_object); - sentry__span_decref(data->span); + DATA_WRITE_LOCK(data) + { + sentry__value_replace(&data->environment, sentry_value_incref(value)); + sentry_value_set_by_key(data->dynamic_sampling_context, "environment", + sentry_value_incref(value)); + } } -static sentry_scope_data_t * -new_data(void) +static sentry_value_t +data_ref_transaction(const sentry_scope_data_t *data) { - sentry_scope_data_t *data = SENTRY_MAKE(sentry_scope_data_t); - if (data) { - sentry__rwlock_init(&data->rwlock); - init_data(data); + sentry_value_t transaction = sentry_value_new_null(); + DATA_READ_LOCK(data) + { + transaction = sentry_value_incref(data->transaction); } - return data; + return transaction; } static void -free_data(sentry_scope_data_t *data) +data_set_transaction(sentry_scope_data_t *data, sentry_value_t value, + const char *transaction, size_t transaction_len) { - if (!data) { - return; + DATA_WRITE_LOCK(data) + { + sentry__value_replace(&data->transaction, sentry_value_incref(value)); + if (data->transaction_object) { + sentry_transaction_set_name_n( + data->transaction_object, transaction, transaction_len); + } + } +} + +static void +data_apply_to_telemetry(const sentry_scope_data_t *data, + sentry_value_t telemetry, sentry_value_t attributes) +{ + sentry_value_t os_name = sentry_value_new_null(); + sentry_value_t os_version = sentry_value_new_null(); + + DATA_READ_LOCK(data) + { + sentry__value_merge_objects_shallow(attributes, data->attributes); + + sentry_value_t trace_id = sentry_value_get_by_key( + sentry_value_get_by_key(data->propagation_context, "trace"), + "trace_id"); + + sentry_value_t parent_span_id = sentry_value_new_object(); + bool has_parent_span = false; + if (data->transaction_object) { + sentry_value_t span_id = sentry_value_get_by_key( + data->transaction_object->inner, "span_id"); + sentry_value_set_by_key( + parent_span_id, "value", sentry_value_incref(span_id)); + trace_id = sentry_value_get_by_key( + data->transaction_object->inner, "trace_id"); + has_parent_span = true; + } else if (data->span) { + sentry_value_t span_id + = sentry_value_get_by_key(data->span->inner, "span_id"); + sentry_value_set_by_key( + parent_span_id, "value", sentry_value_incref(span_id)); + trace_id = sentry_value_get_by_key(data->span->inner, "trace_id"); + has_parent_span = true; + } + sentry_value_set_by_key( + parent_span_id, "type", sentry_value_new_string("string")); + if (has_parent_span + && sentry_value_is_null(sentry_value_get_by_key( + attributes, "sentry.trace.parent_span_id"))) { + sentry_value_set_by_key( + attributes, "sentry.trace.parent_span_id", parent_span_id); + } else { + sentry_value_decref(parent_span_id); + } + if (!sentry_value_is_null(trace_id) + && sentry_value_is_null( + sentry_value_get_by_key(telemetry, "trace_id"))) { + sentry_value_set_by_key( + telemetry, "trace_id", sentry_value_incref(trace_id)); + } + + sentry_value_t os_context + = sentry_value_get_by_key(data->contexts, "os"); + if (!sentry_value_is_null(os_context)) { + os_name = sentry_value_incref( + sentry_value_get_by_key(os_context, "name")); + os_version = sentry_value_incref( + sentry_value_get_by_key(os_context, "version")); + } + } + + if (!sentry_value_is_null(os_name)) { + sentry__value_add_attribute(attributes, os_name, "string", "os.name"); + } else { + sentry_value_decref(os_name); + } + if (!sentry_value_is_null(os_version)) { + sentry__value_add_attribute( + attributes, os_version, "string", "os.version"); + } else { + sentry_value_decref(os_version); } - cleanup_data(data); - sentry__rwlock_free(&data->rwlock); - sentry_free(data); } static bool @@ -299,10 +1171,7 @@ get_scope(void) if (!init_scope(&g_scope, &g_scope_data)) { return &g_scope; } - g_scope.data->user = sentry_value_new_object(); - sentry_value_set_by_key( - g_scope.data->contexts, "os", sentry__get_os_context()); - g_scope.data->client_sdk = get_client_sdk(); + init_global_data(g_scope.data); g_scope_initialized = true; @@ -336,8 +1205,7 @@ sentry__scope_cleanup(void) sentry__mutex_lock(&g_lock); if (g_scope_initialized) { g_scope_initialized = false; - cleanup_data(g_scope.data); - sentry__rwlock_free(&g_scope.data->rwlock); + cleanup_global_data(g_scope.data); g_scope.data = NULL; cleanup_observers(&g_scope); } @@ -548,22 +1416,11 @@ sentry_local_scope_new(void) void sentry__scope_apply_options(sentry_scope_t *scope, sentry_options_t *options) { - if (options->sdk_name) { - sentry_value_t sdk_name = sentry_value_new_string(options->sdk_name); - sentry_value_set_by_key(scope->data->client_sdk, "name", sdk_name); - } - sentry_value_freeze(scope->data->client_sdk); - generate_propagation_context(scope->data->propagation_context); + data_apply_options(scope->data, options); sentry__scope_set_release_n( scope, options->release, sentry__guarded_strlen(options->release)); sentry__scope_set_environment_n(scope, options->environment, sentry__guarded_strlen(options->environment)); - scope->data->attachments = options->attachments; - options->attachments = NULL; - - sentry__ringbuffer_set_max_size( - scope->data->breadcrumbs, options->max_breadcrumbs); - sentry__scope_update_dsc(scope, options); } @@ -583,25 +1440,7 @@ sentry_scope_clear(sentry_scope_t *scope) } sentry__scope_end_notify(scope); - // Keep the propagation and dynamic sampling contexts across clears so - // telemetry captured afterwards continues on the same trace. - sentry_scope_data_t *data = scope->data; - sentry__rwlock_write_lock(&data->rwlock); - bool trace_managed = data->trace_managed; - sentry_value_t propagation_context - = sentry_value_incref(data->propagation_context); - sentry_value_t dynamic_sampling_context - = sentry_value_incref(data->dynamic_sampling_context); - - cleanup_data(data); - init_data(data); - - sentry_value_decref(data->propagation_context); - sentry_value_decref(data->dynamic_sampling_context); - data->propagation_context = propagation_context; - data->dynamic_sampling_context = dynamic_sampling_context; - data->trace_managed = trace_managed; - sentry__rwlock_write_unlock(&data->rwlock); + clear_data(scope->data); } sentry_scope_t * @@ -616,48 +1455,11 @@ sentry_scope_clone(const sentry_scope_t *scope) return NULL; } - clone->data = SENTRY_MAKE(sentry_scope_data_t); + clone->data = clone_data(scope->data); if (!clone->data) { sentry_free(clone); return NULL; } - sentry__rwlock_init(&clone->data->rwlock); - - SCOPE_READ_LOCK(scope) - { - clone->data->release = sentry__value_clone(scope->data->release); - clone->data->environment - = sentry__value_clone(scope->data->environment); - clone->data->transaction - = sentry__value_clone(scope->data->transaction); - clone->data->fingerprint - = sentry__value_clone(scope->data->fingerprint); - clone->data->user = sentry__value_clone(scope->data->user); - clone->data->tags = sentry__value_clone(scope->data->tags); - clone->data->extra = sentry__value_clone(scope->data->extra); - clone->data->contexts = sentry__value_clone(scope->data->contexts); - clone->data->attributes = sentry__value_clone(scope->data->attributes); - clone->data->propagation_context - = sentry__value_clone(scope->data->propagation_context); - clone->data->breadcrumbs - = sentry__ringbuffer_clone(scope->data->breadcrumbs); - clone->data->dynamic_sampling_context - = sentry__value_clone(scope->data->dynamic_sampling_context); - if (sentry_value_is_frozen(scope->data->dynamic_sampling_context)) { - sentry_value_freeze(clone->data->dynamic_sampling_context); - } - clone->data->level = scope->data->level; - clone->data->last_event_id = scope->data->last_event_id; - clone->data->client_sdk = sentry__value_clone(scope->data->client_sdk); - sentry__attachments_extend( - &clone->data->attachments, scope->data->attachments); - - clone->data->transaction_object = scope->data->transaction_object; - sentry__transaction_incref(clone->data->transaction_object); - clone->data->span = scope->data->span; - sentry__span_incref(clone->data->span); - clone->data->trace_managed = scope->data->trace_managed; - } return clone; } @@ -665,88 +1467,56 @@ sentry_scope_clone(const sentry_scope_t *scope) sentry_value_t sentry__scope_get_propagation_context(const sentry_scope_t *scope) { - return scope->data->propagation_context; + return data_get_propagation_context(scope->data); } sentry_value_t sentry__scope_get_trace_context(const sentry_scope_t *scope) { - return sentry_value_get_by_key(scope->data->propagation_context, "trace"); + return data_get_trace_context(scope->data); } void sentry__scope_set_propagation_context( sentry_scope_t *scope, const char *key, sentry_value_t value) { - sentry_value_set_by_key(scope->data->propagation_context, key, value); + data_set_propagation_context(scope->data, key, value); } void sentry__scope_regenerate_propagation_context(sentry_scope_t *scope) { - generate_propagation_context(scope->data->propagation_context); + data_regenerate_propagation_context(scope->data); } bool sentry__scope_is_trace_managed(const sentry_scope_t *scope) { - bool managed = false; - SCOPE_READ_LOCK(scope) { managed = scope->data->trace_managed; } - return managed; + return data_is_trace_managed(scope->data); } void sentry__scope_set_trace_managed(sentry_scope_t *scope, bool managed) { - SCOPE_WRITE_LOCK(scope) { scope->data->trace_managed = managed; } + data_set_trace_managed(scope->data, managed); } sentry_value_t sentry__scope_get_dsc(const sentry_scope_t *scope) { - return scope->data->dynamic_sampling_context; + return data_get_dsc(scope->data); } void sentry__scope_freeze_dsc(sentry_scope_t *scope, sentry_value_t incoming) { - sentry_value_decref(scope->data->dynamic_sampling_context); - sentry_value_t dsc = sentry_value_new_object(); - sentry__value_merge_objects(dsc, incoming); - sentry_value_freeze(dsc); - scope->data->dynamic_sampling_context = dsc; + data_freeze_dsc(scope->data, incoming); } void sentry__scope_update_dsc(sentry_scope_t *scope, const sentry_options_t *options) { - sentry_value_decref(scope->data->dynamic_sampling_context); - sentry_value_t dsc = sentry_value_new_object(); - - if (options->dsn) { - sentry_value_set_by_key(dsc, "public_key", - sentry_value_new_string(options->dsn->public_key)); - } - const char *org_id = sentry__options_get_org_id(options); - if (org_id) { - sentry_value_set_by_key(dsc, "org_id", sentry_value_new_string(org_id)); - } - sentry_value_set_by_key(dsc, "sample_rate", - sentry_value_new_double(options->traces_sample_rate)); - if (options->traces_sampler) { - sentry_value_set_by_key( - dsc, "sample_rate", sentry_value_new_double(1.0)); - } - sentry_value_t sample_rand = sentry_value_get_by_key( - sentry_value_get_by_key(scope->data->propagation_context, "trace"), - "sample_rand"); - sentry_value_set_by_key(dsc, "sample_rand", sample_rand); - sentry_value_incref(sample_rand); - sentry_value_set_by_key(dsc, "release", sentry__scope_ref_release(scope)); - sentry_value_set_by_key( - dsc, "environment", sentry__scope_ref_environment(scope)); - - scope->data->dynamic_sampling_context = dsc; + data_update_dsc(scope->data, options); } #if !defined(SENTRY_PLATFORM_NX) @@ -852,25 +1622,28 @@ sentry__symbolize_stacktrace(sentry_value_t stacktrace) } #endif +#ifdef SENTRY_UNITTEST static sentry_value_t -get_span_or_transaction(const sentry_scope_t *scope) +data_get_span_or_transaction(const sentry_scope_data_t *data) { - if (scope->data->span) { - return scope->data->span->inner; - } else if (scope->data->transaction_object) { - return scope->data->transaction_object->inner; - } else { - return sentry_value_new_null(); + sentry_value_t value = sentry_value_new_null(); + DATA_READ_LOCK(data) + { + if (data->span) { + value = data->span->inner; + } else if (data->transaction_object) { + value = data->transaction_object->inner; + } } + return value; } -#ifdef SENTRY_UNITTEST sentry_value_t sentry__scope_get_span_or_transaction(void) { sentry_value_t result = sentry_value_new_null(); SENTRY_WITH_SCOPE (scope) { - result = get_span_or_transaction(scope); + result = data_get_span_or_transaction(scope->data); } return result; } @@ -961,35 +1734,14 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, PLACE_STRING_VALUE("transaction", transaction); sentry_value_decref(transaction); - PLACE_VALUE("sdk", scope->data->client_sdk); - - SCOPE_READ_LOCK(scope) - { - sentry_value_t event_tags = sentry_value_get_by_key(event, "tags"); - if (sentry_value_is_null(event_tags)) { - if (!sentry_value_is_null(scope->data->tags)) { - PLACE_CLONED_VALUE("tags", scope->data->tags); - } - } else { - sentry__value_merge_objects(event_tags, scope->data->tags); - } + sentry_value_t client_sdk = data_ref_client_sdk(scope->data); + PLACE_VALUE("sdk", client_sdk); + sentry_value_decref(client_sdk); - sentry_value_t event_extra = sentry_value_get_by_key(event, "extra"); - if (sentry_value_is_null(event_extra)) { - if (!sentry_value_is_null(scope->data->extra)) { - PLACE_CLONED_VALUE("extra", scope->data->extra); - } - } else { - sentry__value_merge_objects(event_extra, scope->data->extra); - } - } + data_apply_tags_and_extra(scope->data, event); bool is_transaction = sentry__event_is_transaction(event); - sentry_value_t contexts = sentry_value_new_null(); - SCOPE_READ_LOCK(scope) - { - contexts = sentry__value_clone(scope->data->contexts); - } + sentry_value_t contexts = data_clone_contexts(scope->data); if (is_transaction && !sentry_value_is_null(contexts)) { sentry_value_remove_by_key(contexts, "trace"); } @@ -999,7 +1751,7 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, sentry_value_t scoped_txn_or_span = sentry_value_new_null(); sentry_value_t scope_trace = sentry_value_new_null(); if (!is_transaction) { - scoped_txn_or_span = get_span_or_transaction(scope); + scoped_txn_or_span = data_ref_span_or_transaction(scope->data); scope_trace = sentry__value_get_trace_context(scoped_txn_or_span); } if (!sentry_value_is_null(scope_trace)) { @@ -1015,6 +1767,7 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, } sentry_value_set_by_key(contexts, "trace", scope_trace); } + sentry_value_decref(scoped_txn_or_span); // merge contexts sourced from scope into the event sentry_value_t event_contexts = sentry_value_get_by_key(event, "contexts"); @@ -1022,7 +1775,10 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, if (!is_transaction && sentry_value_is_null(scope_trace) && sentry_value_is_null( sentry_value_get_by_key(event_contexts, "trace"))) { - sentry__value_merge_objects(contexts, scope->data->propagation_context); + sentry_value_t propagation_context + = data_ref_propagation_context(scope->data); + sentry__value_merge_objects(contexts, propagation_context); + sentry_value_decref(propagation_context); } if (sentry_value_is_null(event_contexts)) { PLACE_VALUE("contexts", contexts); @@ -1035,7 +1791,7 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, sentry_value_t event_breadcrumbs = sentry_value_get_by_key(event, "breadcrumbs"); sentry_value_t scope_breadcrumbs - = sentry__ringbuffer_to_list(scope->data->breadcrumbs); + = data_breadcrumbs_to_list(scope->data); sentry_value_set_by_key(event, "breadcrumbs", sentry__value_merge_breadcrumbs(event_breadcrumbs, scope_breadcrumbs, options->max_breadcrumbs)); @@ -1068,7 +1824,7 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, void sentry_scope_add_breadcrumb(sentry_scope_t *scope, sentry_value_t breadcrumb) { - if (sentry__ringbuffer_append(scope->data->breadcrumbs, breadcrumb) == 0) { + if (data_add_breadcrumb(scope->data, breadcrumb)) { SENTRY_SCOPE_NOTIFY(scope, add_breadcrumb, breadcrumb); } } @@ -1076,38 +1832,32 @@ sentry_scope_add_breadcrumb(sentry_scope_t *scope, sentry_value_t breadcrumb) const sentry_ringbuffer_t * sentry__scope_get_breadcrumbs(const sentry_scope_t *scope) { - return scope->data->breadcrumbs; + return data_get_breadcrumbs(scope->data); } sentry_value_t sentry__scope_ref_user(const sentry_scope_t *scope) { - sentry_value_t user = sentry_value_new_null(); - SCOPE_READ_LOCK(scope) { user = sentry_value_incref(scope->data->user); } - return user; + return data_ref_user(scope->data); } void sentry_scope_set_user(sentry_scope_t *scope, sentry_value_t user) { - SCOPE_WRITE_LOCK(scope) - { - sentry__value_replace(&scope->data->user, sentry_value_incref(user)); - } + data_set_user(scope->data, user); SENTRY_SCOPE_NOTIFY_OWNED(scope, set_user, user); } sentry_value_t sentry__scope_get_tags(const sentry_scope_t *scope) { - return scope->data->tags; + return data_get_tags(scope->data); } void sentry_scope_set_tag(sentry_scope_t *scope, const char *key, const char *value) { - if (data_set_by_key(scope->data, &scope->data->tags, key, - sentry_value_new_string(value))) { + if (data_set_tag(scope->data, key, sentry_value_new_string(value))) { SENTRY_SCOPE_NOTIFY(scope, set_tag, key, value); } } @@ -1123,8 +1873,8 @@ sentry_scope_set_tag_n(sentry_scope_t *scope, const char *key, size_t key_len, return; } - if (data_set_by_key_n(scope->data, &scope->data->tags, key, key_len, - sentry_value_incref(tag_value))) { + if (data_set_tag_n( + scope->data, key, key_len, sentry_value_incref(tag_value))) { SENTRY_SCOPE_NOTIFY( scope, set_tag, notify_key, sentry_value_as_string(tag_value)); } @@ -1153,7 +1903,7 @@ sentry_scope_set_tags(sentry_scope_t *scope, sentry_value_t tags) void sentry__scope_remove_tag(sentry_scope_t *scope, const char *key) { - if (data_remove_by_key(scope->data, &scope->data->tags, key)) { + if (data_remove_tag(scope->data, key)) { SENTRY_SCOPE_NOTIFY(scope, remove_tag, key); } } @@ -1162,8 +1912,7 @@ void sentry__scope_remove_tag_n( sentry_scope_t *scope, const char *key, size_t key_len) { - char *k = data_remove_and_take_key_n( - scope->data, &scope->data->tags, key, key_len); + char *k = data_remove_tag_n(scope->data, key, key_len); if (k) { SENTRY_SCOPE_NOTIFY(scope, remove_tag, k); } @@ -1173,15 +1922,14 @@ sentry__scope_remove_tag_n( sentry_value_t sentry__scope_get_extra(const sentry_scope_t *scope) { - return scope->data->extra; + return data_get_extra(scope->data); } void sentry_scope_set_extra( sentry_scope_t *scope, const char *key, sentry_value_t value) { - if (data_set_by_key(scope->data, &scope->data->extra, key, - sentry_value_incref(value))) { + if (data_set_extra(scope->data, key, sentry_value_incref(value))) { SENTRY_SCOPE_NOTIFY(scope, set_extra, key, value); } sentry_value_decref(value); @@ -1197,8 +1945,8 @@ sentry_scope_set_extra_n(sentry_scope_t *scope, const char *key, size_t key_len, return; } - if (data_set_by_key_n(scope->data, &scope->data->extra, key, key_len, - sentry_value_incref(value))) { + if (data_set_extra_n( + scope->data, key, key_len, sentry_value_incref(value))) { SENTRY_SCOPE_NOTIFY(scope, set_extra, notify_key, value); } sentry_free(notify_key); @@ -1208,7 +1956,7 @@ sentry_scope_set_extra_n(sentry_scope_t *scope, const char *key, size_t key_len, void sentry__scope_remove_extra(sentry_scope_t *scope, const char *key) { - if (data_remove_by_key(scope->data, &scope->data->extra, key)) { + if (data_remove_extra(scope->data, key)) { SENTRY_SCOPE_NOTIFY(scope, remove_extra, key); } } @@ -1217,8 +1965,7 @@ void sentry__scope_remove_extra_n( sentry_scope_t *scope, const char *key, size_t key_len) { - char *k = data_remove_and_take_key_n( - scope->data, &scope->data->extra, key, key_len); + char *k = data_remove_extra_n(scope->data, key, key_len); if (k) { SENTRY_SCOPE_NOTIFY(scope, remove_extra, k); } @@ -1243,40 +1990,39 @@ sentry_scope_set_attribute_n(sentry_scope_t *scope, const char *key, sentry_value_decref(attribute); return; } - sentry_value_set_by_key_n(scope->data->attributes, key, key_len, attribute); + data_set_attribute_n(scope->data, key, key_len, attribute); } sentry_value_t sentry__scope_get_attributes(const sentry_scope_t *scope) { - return scope->data->attributes; + return data_get_attributes(scope->data); } void sentry_scope_remove_attribute(sentry_scope_t *scope, const char *key) { - sentry_value_remove_by_key(scope->data->attributes, key); + data_remove_attribute(scope->data, key); } void sentry_scope_remove_attribute_n( sentry_scope_t *scope, const char *key, size_t key_len) { - sentry_value_remove_by_key_n(scope->data->attributes, key, key_len); + data_remove_attribute_n(scope->data, key, key_len); } sentry_value_t sentry__scope_get_contexts(const sentry_scope_t *scope) { - return scope->data->contexts; + return data_get_contexts(scope->data); } void sentry_scope_set_context( sentry_scope_t *scope, const char *key, sentry_value_t value) { - if (data_set_by_key(scope->data, &scope->data->contexts, key, - sentry_value_incref(value))) { + if (data_set_context(scope->data, key, sentry_value_incref(value))) { SENTRY_SCOPE_NOTIFY(scope, set_context, key, value); } sentry_value_decref(value); @@ -1292,8 +2038,8 @@ sentry_scope_set_context_n(sentry_scope_t *scope, const char *key, return; } - if (data_set_by_key_n(scope->data, &scope->data->contexts, key, key_len, - sentry_value_incref(value))) { + if (data_set_context_n( + scope->data, key, key_len, sentry_value_incref(value))) { SENTRY_SCOPE_NOTIFY(scope, set_context, notify_key, value); } sentry_free(notify_key); @@ -1318,7 +2064,7 @@ sentry_scope_update_context_n(sentry_scope_t *scope, const char *key, return; } - if (data_update_context_by_key_n( + if (data_update_context_n( scope->data, key, key_len, sentry_value_incref(value))) { SENTRY_SCOPE_NOTIFY(scope, set_context, notify_key, value); } @@ -1329,7 +2075,7 @@ sentry_scope_update_context_n(sentry_scope_t *scope, const char *key, void sentry__scope_remove_context(sentry_scope_t *scope, const char *key) { - if (data_remove_by_key(scope->data, &scope->data->contexts, key)) { + if (data_remove_context(scope->data, key)) { SENTRY_SCOPE_NOTIFY(scope, remove_context, key); } } @@ -1338,8 +2084,7 @@ void sentry__scope_remove_context_n( sentry_scope_t *scope, const char *key, size_t key_len) { - char *k = data_remove_and_take_key_n( - scope->data, &scope->data->contexts, key, key_len); + char *k = data_remove_context_n(scope->data, key, key_len); if (k) { SENTRY_SCOPE_NOTIFY(scope, remove_context, k); } @@ -1349,12 +2094,7 @@ sentry__scope_remove_context_n( sentry_value_t sentry__scope_ref_fingerprint(const sentry_scope_t *scope) { - sentry_value_t fingerprint = sentry_value_new_null(); - SCOPE_READ_LOCK(scope) - { - fingerprint = sentry_value_incref(scope->data->fingerprint); - } - return fingerprint; + return data_ref_fingerprint(scope->data); } void @@ -1420,11 +2160,7 @@ sentry_scope_set_fingerprints( return; } - SCOPE_WRITE_LOCK(scope) - { - sentry__value_replace( - &scope->data->fingerprint, sentry_value_incref(fingerprints)); - } + data_set_fingerprint(scope->data, fingerprints); SENTRY_SCOPE_NOTIFY_OWNED(scope, set_fingerprint, fingerprints); } @@ -1432,32 +2168,26 @@ void sentry_scope_remove_fingerprint(sentry_scope_t *scope) { sentry_value_t fingerprint = sentry_value_new_null(); - SCOPE_WRITE_LOCK(scope) - { - sentry__value_replace( - &scope->data->fingerprint, sentry_value_incref(fingerprint)); - } + data_set_fingerprint(scope->data, fingerprint); SENTRY_SCOPE_NOTIFY_OWNED(scope, set_fingerprint, fingerprint); } sentry_level_t sentry__scope_get_level(const sentry_scope_t *scope) { - sentry_level_t level = SENTRY_LEVEL_ERROR; - SCOPE_READ_LOCK(scope) { level = scope->data->level; } - return level; + return data_get_level(scope->data); } sentry_value_t -sentry__scope_get_client_sdk(const sentry_scope_t *scope) +sentry__scope_ref_client_sdk(const sentry_scope_t *scope) { - return scope->data->client_sdk; + return data_ref_client_sdk(scope->data); } void sentry_scope_set_level(sentry_scope_t *scope, sentry_level_t level) { - SCOPE_WRITE_LOCK(scope) { scope->data->level = level; } + data_set_level(scope->data, level); SENTRY_SCOPE_NOTIFY(scope, set_level, level); } @@ -1487,17 +2217,16 @@ sentry_scope_set_span(sentry_scope_t *scope, sentry_span_t *span) sentry_value_t sentry__scope_get_attachments(const sentry_scope_t *scope) { - return scope->data->attachments; + return data_get_attachments(scope->data); } sentry_value_t sentry__scope_add_attachment(sentry_scope_t *scope, sentry_value_t attachment) { - size_t len = sentry_value_get_length(scope->data->attachments); + bool did_add = false; sentry_value_t added - = sentry__attachments_add(&scope->data->attachments, attachment); - if (!sentry_value_is_null(added) - && sentry_value_get_length(scope->data->attachments) > len) { + = data_add_attachment(scope->data, attachment, &did_add); + if (did_add) { SENTRY_SCOPE_NOTIFY(scope, add_attachment, added); } return added; @@ -1506,17 +2235,14 @@ sentry__scope_add_attachment(sentry_scope_t *scope, sentry_value_t attachment) sentry_value_t sentry__scope_take_attachments(sentry_scope_t *scope) { - sentry_value_t attachments = scope->data->attachments; - scope->data->attachments = sentry__attachments_new(); - return attachments; + return data_take_attachments(scope->data); } sentry_value_t sentry__scope_remove_attachment( sentry_scope_t *scope, const sentry_uuid_t *attachment_id) { - return sentry__attachments_remove( - scope->data->attachments, attachment_id); + return data_remove_attachment(scope->data, attachment_id); } sentry_uuid_t @@ -1536,135 +2262,71 @@ sentry_scope_add_attachment(sentry_scope_t *scope, sentry_value_t attachment) sentry_transaction_t * sentry__scope_get_transaction_object(const sentry_scope_t *scope) { - return scope->data->transaction_object; -} - -static bool -value_has_span_id(sentry_value_t value, const char *span_id) -{ - const char *value_span_id - = sentry_value_as_string(sentry_value_get_by_key(value, "span_id")); - return sentry__string_eq(value_span_id, span_id); + return data_get_transaction_object(scope->data); } void sentry__scope_set_transaction_object( sentry_scope_t *scope, sentry_transaction_t *transaction) { - sentry__transaction_incref(transaction); - sentry__span_decref(scope->data->span); - scope->data->span = NULL; - sentry__transaction_decref(scope->data->transaction_object); - scope->data->transaction_object = transaction; + data_set_transaction_object(scope->data, transaction); } bool sentry__scope_remove_transaction_object( sentry_scope_t *scope, sentry_transaction_t *transaction) { - if (!transaction || scope->data->transaction_object != transaction) { - return false; - } - - scope->data->transaction_object = NULL; - sentry__transaction_decref(transaction); - return true; + return data_remove_transaction_object(scope->data, transaction); } bool sentry__scope_remove_transaction_value( sentry_scope_t *scope, sentry_value_t transaction) { - const char *span_id = sentry_value_as_string( - sentry_value_get_by_key(transaction, "span_id")); - if (!scope->data->transaction_object - || !value_has_span_id( - scope->data->transaction_object->inner, span_id)) { - return false; - } - - sentry_transaction_t *transaction_object = scope->data->transaction_object; - scope->data->transaction_object = NULL; - sentry__transaction_decref(transaction_object); - return true; + return data_remove_transaction_value(scope->data, transaction); } bool sentry__scope_restore_transaction_object( sentry_scope_t *scope, sentry_transaction_t *transaction) { - if (scope->data->transaction_object || !transaction) { - return false; - } - - scope->data->transaction_object = transaction; - return true; + return data_restore_transaction_object(scope->data, transaction); } sentry_span_t * sentry__scope_get_span(const sentry_scope_t *scope) { - return scope->data->span; + return data_get_span(scope->data); } void sentry__scope_set_span(sentry_scope_t *scope, sentry_span_t *span) { - sentry__span_incref(span); - sentry__transaction_decref(scope->data->transaction_object); - scope->data->transaction_object = NULL; - sentry__span_decref(scope->data->span); - scope->data->span = span; + data_set_span(scope->data, span); } bool sentry__scope_remove_span(sentry_scope_t *scope, sentry_span_t *span) { - if (!span || scope->data->span != span) { - return false; - } - - scope->data->span = NULL; - sentry__span_decref(span); - return true; + return data_remove_span(scope->data, span); } bool sentry__scope_remove_span_value(sentry_scope_t *scope, sentry_value_t span) { - const char *span_id - = sentry_value_as_string(sentry_value_get_by_key(span, "span_id")); - if (!scope->data->span - || !value_has_span_id(scope->data->span->inner, span_id)) { - return false; - } - - sentry_span_t *scope_span = scope->data->span; - scope->data->span = NULL; - sentry__span_decref(scope_span); - return true; + return data_remove_span_value(scope->data, span); } bool sentry__scope_restore_span(sentry_scope_t *scope, sentry_span_t *span) { - if (scope->data->span || !span) { - return false; - } - - scope->data->span = span; - return true; + return data_restore_span(scope->data, span); } sentry_value_t sentry__scope_ref_release(const sentry_scope_t *scope) { - sentry_value_t release = sentry_value_new_null(); - SCOPE_READ_LOCK(scope) - { - release = sentry_value_incref(scope->data->release); - } - return release; + return data_ref_release(scope->data); } void @@ -1672,25 +2334,14 @@ sentry__scope_set_release_n( sentry_scope_t *scope, const char *release, size_t release_len) { sentry_value_t value = sentry_value_new_string_n(release, release_len); - SCOPE_WRITE_LOCK(scope) - { - sentry__value_replace( - &scope->data->release, sentry_value_incref(value)); - sentry_value_set_by_key(scope->data->dynamic_sampling_context, - "release", sentry_value_incref(value)); - } + data_set_release(scope->data, value); SENTRY_SCOPE_NOTIFY_OWNED(scope, set_release, value); } sentry_value_t sentry__scope_ref_environment(const sentry_scope_t *scope) { - sentry_value_t environment = sentry_value_new_null(); - SCOPE_READ_LOCK(scope) - { - environment = sentry_value_incref(scope->data->environment); - } - return environment; + return data_ref_environment(scope->data); } void @@ -1699,25 +2350,14 @@ sentry__scope_set_environment_n( { sentry_value_t value = sentry_value_new_string_n(environment, environment_len); - SCOPE_WRITE_LOCK(scope) - { - sentry__value_replace( - &scope->data->environment, sentry_value_incref(value)); - sentry_value_set_by_key(scope->data->dynamic_sampling_context, - "environment", sentry_value_incref(value)); - } + data_set_environment(scope->data, value); SENTRY_SCOPE_NOTIFY_OWNED(scope, set_environment, value); } sentry_value_t sentry__scope_ref_transaction(const sentry_scope_t *scope) { - sentry_value_t transaction = sentry_value_new_null(); - SCOPE_READ_LOCK(scope) - { - transaction = sentry_value_incref(scope->data->transaction); - } - return transaction; + return data_ref_transaction(scope->data); } void @@ -1726,15 +2366,7 @@ sentry__scope_set_transaction_n( { sentry_value_t value = sentry_value_new_string_n(transaction, transaction_len); - SCOPE_WRITE_LOCK(scope) - { - sentry__value_replace( - &scope->data->transaction, sentry_value_incref(value)); - if (scope->data->transaction_object) { - sentry_transaction_set_name_n( - scope->data->transaction_object, transaction, transaction_len); - } - } + data_set_transaction(scope->data, value, transaction, transaction_len); SENTRY_SCOPE_NOTIFY_OWNED(scope, set_transaction, value); } @@ -1807,45 +2439,7 @@ void sentry__scope_apply_to_telemetry(const sentry_scope_t *scope, sentry_value_t telemetry, sentry_value_t attributes) { - sentry__value_merge_objects_shallow(attributes, scope->data->attributes); - - // a span on the scope MUST take precedence over the propagation context - sentry_value_t trace_id = sentry_value_get_by_key( - sentry_value_get_by_key(scope->data->propagation_context, "trace"), - "trace_id"); - - sentry_value_t parent_span_id = sentry_value_new_object(); - if (scope->data->transaction_object) { - sentry_value_t span_id = sentry_value_get_by_key( - scope->data->transaction_object->inner, "span_id"); - sentry_value_incref(span_id); - sentry_value_set_by_key(parent_span_id, "value", span_id); - trace_id = sentry_value_get_by_key( - scope->data->transaction_object->inner, "trace_id"); - } else if (scope->data->span) { - sentry_value_t span_id - = sentry_value_get_by_key(scope->data->span->inner, "span_id"); - sentry_value_incref(span_id); - sentry_value_set_by_key(parent_span_id, "value", span_id); - trace_id - = sentry_value_get_by_key(scope->data->span->inner, "trace_id"); - } - sentry_value_set_by_key( - parent_span_id, "type", sentry_value_new_string("string")); - if ((scope->data->transaction_object || scope->data->span) - && sentry_value_is_null(sentry_value_get_by_key( - attributes, "sentry.trace.parent_span_id"))) { - sentry_value_set_by_key( - attributes, "sentry.trace.parent_span_id", parent_span_id); - } else { - sentry_value_decref(parent_span_id); - } - if (!sentry_value_is_null(trace_id) - && sentry_value_is_null( - sentry_value_get_by_key(telemetry, "trace_id"))) { - sentry_value_incref(trace_id); - sentry_value_set_by_key(telemetry, "trace_id", trace_id); - } + data_apply_to_telemetry(scope->data, telemetry, attributes); sentry_value_t user = sentry__scope_ref_user(scope); if (!sentry_value_is_null(user)) { @@ -1873,30 +2467,6 @@ sentry__scope_apply_to_telemetry(const sentry_scope_t *scope, } sentry_value_decref(user); - sentry_value_t os_name = sentry_value_new_null(); - sentry_value_t os_version = sentry_value_new_null(); - SCOPE_READ_LOCK(scope) - { - sentry_value_t os_context - = sentry_value_get_by_key(scope->data->contexts, "os"); - if (!sentry_value_is_null(os_context)) { - os_name = sentry_value_incref( - sentry_value_get_by_key(os_context, "name")); - os_version = sentry_value_incref( - sentry_value_get_by_key(os_context, "version")); - } - } - if (!sentry_value_is_null(os_name)) { - sentry__value_add_attribute(attributes, os_name, "string", "os.name"); - } else { - sentry_value_decref(os_name); - } - if (!sentry_value_is_null(os_version)) { - sentry__value_add_attribute( - attributes, os_version, "string", "os.version"); - } else { - sentry_value_decref(os_version); - } sentry_value_t environment = sentry__scope_ref_environment(scope); if (!sentry_value_is_null(environment)) { sentry__value_add_attribute(attributes, @@ -1915,11 +2485,7 @@ sentry__scope_apply_to_telemetry(const sentry_scope_t *scope, sentry_uuid_t sentry_scope_get_last_event_id(const sentry_scope_t *scope) { - sentry_uuid_t event_id = sentry_uuid_nil(); - if (scope) { - SCOPE_READ_LOCK(scope) { event_id = scope->data->last_event_id; } - } - return event_id; + return scope ? data_get_last_event_id(scope->data) : sentry_uuid_nil(); } void @@ -1927,7 +2493,7 @@ sentry__scope_set_last_event_id( sentry_scope_t *scope, sentry_uuid_t event_id) { if (scope) { - SCOPE_WRITE_LOCK(scope) { scope->data->last_event_id = event_id; } + data_set_last_event_id(scope->data, event_id); } } diff --git a/src/sentry_scope.h b/src/sentry_scope.h index e98e213a31..f04359f989 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -146,7 +146,7 @@ void sentry__scope_set_fingerprint_nva(sentry_scope_t *scope, const char *fingerprint, size_t fingerprint_len, va_list va); sentry_value_t sentry__scope_ref_user(const sentry_scope_t *scope); sentry_level_t sentry__scope_get_level(const sentry_scope_t *scope); -sentry_value_t sentry__scope_get_client_sdk(const sentry_scope_t *scope); +sentry_value_t sentry__scope_ref_client_sdk(const sentry_scope_t *scope); sentry_value_t sentry__scope_get_attachments(const sentry_scope_t *scope); sentry_value_t sentry__scope_add_attachment( diff --git a/tests/unit/test_basic.c b/tests/unit/test_basic.c index 9e4a06667a..b0bc0971ce 100644 --- a/tests/unit/test_basic.c +++ b/tests/unit/test_basic.c @@ -424,9 +424,9 @@ SENTRY_TEST(client_sdk_integrations) sentry_init(options); SENTRY_WITH_SCOPE (scope) { + sentry_value_t client_sdk = sentry__scope_ref_client_sdk(scope); sentry_value_t integrations - = sentry_value_get_by_key( - sentry__scope_get_client_sdk(scope), "integrations"); + = sentry_value_get_by_key(client_sdk, "integrations"); size_t integration_count = sentry_value_get_length(integrations); TEST_CHECK(integration_count > 0); TEST_CHECK_STRING_EQUAL( @@ -450,6 +450,7 @@ SENTRY_TEST(client_sdk_integrations) sentry_value_get_by_index(integrations, integration_index)), "qt"); #endif + sentry_value_decref(client_sdk); } sentry_close(); From fb62057b9dec3f9d512df82bcf9b3eed7890f5d2 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 6 Aug 2026 15:24:04 +0200 Subject: [PATCH 10/22] ref containers --- src/integrations/sentry_integration_wer.c | 10 +- src/sentry_scope.c | 40 +++--- src/sentry_scope.h | 8 +- tests/unit/test_scope.c | 161 ++++++++++++++-------- 4 files changed, 138 insertions(+), 81 deletions(-) diff --git a/src/integrations/sentry_integration_wer.c b/src/integrations/sentry_integration_wer.c index e2895bc31c..87130a7be0 100644 --- a/src/integrations/sentry_integration_wer.c +++ b/src/integrations/sentry_integration_wer.c @@ -207,8 +207,9 @@ wer_clear(void *data) return; } - sentry__value_foreach_key_value( - sentry__scope_get_tags(scope), wer_cleanup_tag, wer_data); + sentry_value_t tags = sentry__scope_ref_tags(scope); + sentry__value_foreach_key_value(tags, wer_cleanup_tag, wer_data); + sentry_value_decref(tags); sentry_value_t attachments = sentry__scope_get_attachments(scope); size_t len = sentry_value_get_length(attachments); @@ -259,8 +260,9 @@ unregister_wer( return; } - sentry__value_foreach_key_value( - sentry__scope_get_tags(scope), wer_cleanup_tag, wer_data); + sentry_value_t tags = sentry__scope_ref_tags(scope); + sentry__value_foreach_key_value(tags, wer_cleanup_tag, wer_data); + sentry_value_decref(tags); sentry_value_t attachments = sentry__scope_get_attachments(scope); size_t len = sentry_value_get_length(attachments); diff --git a/src/sentry_scope.c b/src/sentry_scope.c index c277367cd2..8aaa1b36b4 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -555,9 +555,11 @@ data_set_user(sentry_scope_data_t *data, sentry_value_t user) } static sentry_value_t -data_get_tags(const sentry_scope_data_t *data) +data_ref_tags(const sentry_scope_data_t *data) { - return data->tags; + sentry_value_t tags = sentry_value_new_null(); + DATA_READ_LOCK(data) { tags = sentry_value_incref(data->tags); } + return tags; } static bool @@ -608,9 +610,11 @@ data_remove_tag_n(sentry_scope_data_t *data, const char *key, size_t key_len) } static sentry_value_t -data_get_extra(const sentry_scope_data_t *data) +data_ref_extra(const sentry_scope_data_t *data) { - return data->extra; + sentry_value_t extra = sentry_value_new_null(); + DATA_READ_LOCK(data) { extra = sentry_value_incref(data->extra); } + return extra; } static bool @@ -661,9 +665,11 @@ data_remove_extra_n(sentry_scope_data_t *data, const char *key, size_t key_len) } static sentry_value_t -data_get_attributes(const sentry_scope_data_t *data) +data_ref_attributes(const sentry_scope_data_t *data) { - return data->attributes; + sentry_value_t attributes = sentry_value_new_null(); + DATA_READ_LOCK(data) { attributes = sentry_value_incref(data->attributes); } + return attributes; } static bool @@ -697,9 +703,11 @@ data_remove_attribute_n( } static sentry_value_t -data_get_contexts(const sentry_scope_data_t *data) +data_ref_contexts(const sentry_scope_data_t *data) { - return data->contexts; + sentry_value_t contexts = sentry_value_new_null(); + DATA_READ_LOCK(data) { contexts = sentry_value_incref(data->contexts); } + return contexts; } static bool @@ -1849,9 +1857,9 @@ sentry_scope_set_user(sentry_scope_t *scope, sentry_value_t user) } sentry_value_t -sentry__scope_get_tags(const sentry_scope_t *scope) +sentry__scope_ref_tags(const sentry_scope_t *scope) { - return data_get_tags(scope->data); + return data_ref_tags(scope->data); } void @@ -1920,9 +1928,9 @@ sentry__scope_remove_tag_n( } sentry_value_t -sentry__scope_get_extra(const sentry_scope_t *scope) +sentry__scope_ref_extra(const sentry_scope_t *scope) { - return data_get_extra(scope->data); + return data_ref_extra(scope->data); } void @@ -1994,9 +2002,9 @@ sentry_scope_set_attribute_n(sentry_scope_t *scope, const char *key, } sentry_value_t -sentry__scope_get_attributes(const sentry_scope_t *scope) +sentry__scope_ref_attributes(const sentry_scope_t *scope) { - return data_get_attributes(scope->data); + return data_ref_attributes(scope->data); } void @@ -2013,9 +2021,9 @@ sentry_scope_remove_attribute_n( } sentry_value_t -sentry__scope_get_contexts(const sentry_scope_t *scope) +sentry__scope_ref_contexts(const sentry_scope_t *scope) { - return data_get_contexts(scope->data); + return data_ref_contexts(scope->data); } void diff --git a/src/sentry_scope.h b/src/sentry_scope.h index f04359f989..b9c7329c21 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -154,19 +154,19 @@ sentry_value_t sentry__scope_add_attachment( sentry_value_t sentry__scope_take_attachments(sentry_scope_t *scope); sentry_value_t sentry__scope_remove_attachment( sentry_scope_t *scope, const sentry_uuid_t *attachment_id); -sentry_value_t sentry__scope_get_tags(const sentry_scope_t *scope); +sentry_value_t sentry__scope_ref_tags(const sentry_scope_t *scope); void sentry__scope_remove_tag(sentry_scope_t *scope, const char *key); void sentry__scope_remove_tag_n( sentry_scope_t *scope, const char *key, size_t key_len); -sentry_value_t sentry__scope_get_extra(const sentry_scope_t *scope); +sentry_value_t sentry__scope_ref_extra(const sentry_scope_t *scope); void sentry__scope_remove_extra(sentry_scope_t *scope, const char *key); void sentry__scope_remove_extra_n( sentry_scope_t *scope, const char *key, size_t key_len); -sentry_value_t sentry__scope_get_attributes(const sentry_scope_t *scope); +sentry_value_t sentry__scope_ref_attributes(const sentry_scope_t *scope); -sentry_value_t sentry__scope_get_contexts(const sentry_scope_t *scope); +sentry_value_t sentry__scope_ref_contexts(const sentry_scope_t *scope); void sentry__scope_remove_context(sentry_scope_t *scope, const char *key); void sentry__scope_remove_context_n( sentry_scope_t *scope, const char *key, size_t key_len); diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index a39a9146b9..00901fcb8c 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -13,6 +13,28 @@ #define TEST_CHECK_UUID_EQUAL(Actual, Expected) \ TEST_CHECK(memcmp(&(Actual), &(Expected), sizeof(sentry_uuid_t)) == 0) +typedef sentry_value_t (*scope_value_ref_t)(const sentry_scope_t *scope); + +static sentry_value_t +scope_value_get_by_key( + scope_value_ref_t ref, const sentry_scope_t *scope, const char *key) +{ + sentry_value_t values = ref(scope); + sentry_value_t value + = sentry_value_incref(sentry_value_get_by_key(values, key)); + sentry_value_decref(values); + return value; +} + +static size_t +scope_value_get_length(scope_value_ref_t ref, const sentry_scope_t *scope) +{ + sentry_value_t value = ref(scope); + size_t length = sentry_value_get_length(value); + sentry_value_decref(value); + return length; +} + SENTRY_TEST(scope_contexts) { SENTRY_TEST_OPTIONS_NEW(options); @@ -123,14 +145,15 @@ SENTRY_TEST(scope_update_context) sentry_update_context("device", device); SENTRY_WITH_SCOPE (scope) { - sentry_value_t ctx = sentry_value_get_by_key( - sentry__scope_get_contexts(scope), "device"); + sentry_value_t ctx = scope_value_get_by_key( + sentry__scope_ref_contexts, scope, "device"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(ctx, "model")), "Xbox Series X"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(ctx, "family")), "Xbox"); + sentry_value_decref(ctx); } } @@ -143,8 +166,8 @@ SENTRY_TEST(scope_update_context) sentry_update_context("device", extra); SENTRY_WITH_SCOPE (scope) { - sentry_value_t ctx = sentry_value_get_by_key( - sentry__scope_get_contexts(scope), "device"); + sentry_value_t ctx = scope_value_get_by_key( + sentry__scope_ref_contexts, scope, "device"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(ctx, "model")), "PC"); @@ -155,6 +178,7 @@ SENTRY_TEST(scope_update_context) sentry_value_as_string( sentry_value_get_by_key(ctx, "cpu_description")), "some cpu"); + sentry_value_decref(ctx); } } @@ -166,11 +190,12 @@ SENTRY_TEST(scope_update_context) sentry_value_set_by_key(os, "name", sentry_value_new_string("SteamOS")); sentry_scope_update_context(local_scope, "os", os); - sentry_value_t ctx = sentry_value_get_by_key( - sentry__scope_get_contexts(local_scope), "os"); + sentry_value_t ctx = scope_value_get_by_key( + sentry__scope_ref_contexts, local_scope, "os"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(ctx, "name")), "SteamOS"); + sentry_value_decref(ctx); // scoped update overwrites existing keys sentry_value_t os2 = sentry_value_new_object(); @@ -178,14 +203,15 @@ SENTRY_TEST(scope_update_context) sentry_value_set_by_key(os2, "version", sentry_value_new_string("6.1")); sentry_scope_update_context(local_scope, "os", os2); - ctx = sentry_value_get_by_key( - sentry__scope_get_contexts(local_scope), "os"); + ctx = scope_value_get_by_key( + sentry__scope_ref_contexts, local_scope, "os"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(ctx, "name")), "Linux"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(ctx, "version")), "6.1"); + sentry_value_decref(ctx); sentry_scope_free(local_scope); } @@ -1113,20 +1139,22 @@ SENTRY_TEST(scope_clone) // scope values must not be corrupted by before_send modifications SENTRY_WITH_SCOPE (scope) { sentry_value_t scope_gpu - = sentry_value_get_by_key(sentry__scope_get_contexts(scope), "gpu"); + = scope_value_get_by_key(sentry__scope_ref_contexts, scope, "gpu"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(scope_gpu, "name")), "original"); TEST_CHECK(sentry_value_is_null( sentry_value_get_by_key(scope_gpu, "injected"))); + sentry_value_decref(scope_gpu); sentry_value_t scope_data - = sentry_value_get_by_key(sentry__scope_get_extra(scope), "data"); + = scope_value_get_by_key(sentry__scope_ref_extra, scope, "data"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(scope_data, "key")), "original"); TEST_CHECK(sentry_value_is_null( sentry_value_get_by_key(scope_data, "injected"))); + sentry_value_decref(scope_data); sentry_value_t scope_user = sentry__scope_ref_user(scope); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( @@ -1155,7 +1183,7 @@ SENTRY_TEST(scope_global_attributes) sentry_set_attribute("valid_key", valid_attr); SENTRY_WITH_SCOPE (scope) { - sentry_value_t attributes = sentry__scope_get_attributes(scope); + sentry_value_t attributes = sentry__scope_ref_attributes(scope); sentry_value_t retrieved_attr = sentry_value_get_by_key(attributes, "valid_key"); @@ -1167,6 +1195,7 @@ SENTRY_TEST(scope_global_attributes) TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( retrieved_attr, "value")), "test_value"); + sentry_value_decref(attributes); } // Test that invalid attributes (missing 'value' or 'type') are not set @@ -1177,12 +1206,13 @@ SENTRY_TEST(scope_global_attributes) sentry_set_attribute("invalid_no_value", invalid_attr_no_value); SENTRY_WITH_SCOPE (scope) { - sentry_value_t attributes = sentry__scope_get_attributes(scope); + sentry_value_t attributes = sentry__scope_ref_attributes(scope); sentry_value_t retrieved_attr = sentry_value_get_by_key(attributes, "invalid_no_value"); // Check that the attribute was NOT set TEST_CHECK(sentry_value_is_null(retrieved_attr)); + sentry_value_decref(attributes); } // Test invalid attribute missing 'type' @@ -1193,24 +1223,26 @@ SENTRY_TEST(scope_global_attributes) sentry_set_attribute("invalid_no_type", invalid_attr_no_type); SENTRY_WITH_SCOPE (scope) { - sentry_value_t attributes = sentry__scope_get_attributes(scope); + sentry_value_t attributes = sentry__scope_ref_attributes(scope); sentry_value_t retrieved_attr = sentry_value_get_by_key(attributes, "invalid_no_type"); // Check that the attribute was NOT set TEST_CHECK(sentry_value_is_null(retrieved_attr)); + sentry_value_decref(attributes); } // Test removing an attribute sentry_remove_attribute("valid_key"); SENTRY_WITH_SCOPE (scope) { - sentry_value_t attributes = sentry__scope_get_attributes(scope); + sentry_value_t attributes = sentry__scope_ref_attributes(scope); sentry_value_t retrieved_attr = sentry_value_get_by_key(attributes, "valid_key"); // Check that the attribute was removed TEST_CHECK(sentry_value_is_null(retrieved_attr)); + sentry_value_decref(attributes); } // Test setting attribute with _n variant @@ -1219,7 +1251,7 @@ SENTRY_TEST(scope_global_attributes) sentry_set_attribute_n("key_n", 5, attr_n); SENTRY_WITH_SCOPE (scope) { - sentry_value_t attributes = sentry__scope_get_attributes(scope); + sentry_value_t attributes = sentry__scope_ref_attributes(scope); sentry_value_t retrieved_attr = sentry_value_get_by_key(attributes, "key_n"); @@ -1234,6 +1266,7 @@ SENTRY_TEST(scope_global_attributes) TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( retrieved_attr, "unit")), "percent"); + sentry_value_decref(attributes); } sentry_close(); @@ -1254,7 +1287,7 @@ SENTRY_TEST(scope_local_attributes) sentry_value_new_attribute(sentry_value_new_string("global"), NULL)); SENTRY_WITH_SCOPE (global_scope) { - sentry_value_t attributes = sentry__scope_get_attributes(global_scope); + sentry_value_t attributes = sentry__scope_ref_attributes(global_scope); // Verify global attributes are set TEST_CHECK_STRING_EQUAL( @@ -1269,6 +1302,7 @@ SENTRY_TEST(scope_local_attributes) sentry_value_as_string(sentry_value_get_by_key( sentry_value_get_by_key(attributes, "scope"), "value")), "global"); + sentry_value_decref(attributes); } SENTRY_WITH_SCOPE (global_scope) { @@ -1283,7 +1317,7 @@ SENTRY_TEST(scope_local_attributes) sentry_value_new_attribute(sentry_value_new_string("local"), NULL)); sentry_value_t local_attributes - = sentry__scope_get_attributes(local_scope); + = sentry__scope_ref_attributes(local_scope); // Verify local attributes are set TEST_CHECK_STRING_EQUAL( @@ -1301,7 +1335,7 @@ SENTRY_TEST(scope_local_attributes) // Verify global scope still has its own attributes sentry_value_t global_attributes - = sentry__scope_get_attributes(global_scope); + = sentry__scope_ref_attributes(global_scope); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key( sentry_value_get_by_key(global_attributes, "all"), "value")), @@ -1310,6 +1344,8 @@ SENTRY_TEST(scope_local_attributes) sentry_value_as_string(sentry_value_get_by_key( sentry_value_get_by_key(global_attributes, "global"), "value")), "global"); + sentry_value_decref(global_attributes); + sentry_value_decref(local_attributes); sentry_scope_free(local_scope); } @@ -1318,7 +1354,7 @@ SENTRY_TEST(scope_local_attributes) sentry_remove_attribute("all"); SENTRY_WITH_SCOPE (scope) { - sentry_value_t attributes = sentry__scope_get_attributes(scope); + sentry_value_t attributes = sentry__scope_ref_attributes(scope); TEST_CHECK( sentry_value_is_null(sentry_value_get_by_key(attributes, "all"))); // Other attributes should still exist @@ -1326,6 +1362,7 @@ SENTRY_TEST(scope_local_attributes) sentry_value_get_by_key(attributes, "global"))); TEST_CHECK(!sentry_value_is_null( sentry_value_get_by_key(attributes, "scope"))); + sentry_value_decref(attributes); } // Test _n variants with local scope @@ -1335,7 +1372,7 @@ SENTRY_TEST(scope_local_attributes) sentry_value_new_attribute(sentry_value_new_int32(100), "percent")); sentry_value_t local_attributes - = sentry__scope_get_attributes(local_scope); + = sentry__scope_ref_attributes(local_scope); sentry_value_t attr = sentry_value_get_by_key(local_attributes, "test_key"); @@ -1353,6 +1390,7 @@ SENTRY_TEST(scope_local_attributes) sentry_scope_remove_attribute_n(local_scope, "test_key", 8); TEST_CHECK(sentry_value_is_null( sentry_value_get_by_key(local_attributes, "test_key"))); + sentry_value_decref(local_attributes); sentry_scope_free(local_scope); } @@ -1368,9 +1406,10 @@ SENTRY_TEST(scope_local_attributes) sentry_scope_set_attribute(local_scope, "invalid", invalid_attr); sentry_value_t local_attributes - = sentry__scope_get_attributes(local_scope); + = sentry__scope_ref_attributes(local_scope); TEST_CHECK(sentry_value_is_null( sentry_value_get_by_key(local_attributes, "invalid"))); + sentry_value_decref(local_attributes); sentry_scope_free(local_scope); } @@ -1680,7 +1719,7 @@ SENTRY_TEST(scope_observer_clear) sentry_scope_clear(scope); TEST_CHECK(d.was_cleared); TEST_CHECK(sentry__scope_has_observers(scope)); - TEST_CHECK(sentry_value_get_length(sentry__scope_get_tags(scope)) == 0); + TEST_CHECK(scope_value_get_length(sentry__scope_ref_tags, scope) == 0); d.was_called = false; sentry_scope_set_tag(scope, "after", "clear"); @@ -1707,7 +1746,7 @@ SENTRY_TEST(scope_observer_clear) TEST_CHECK(observer_data.was_called); TEST_CHECK(observer_data.was_cleared); TEST_CHECK(sentry__scope_has_observers(scope)); - TEST_CHECK(sentry_value_get_length(sentry__scope_get_tags(scope)) == 0); + TEST_CHECK(scope_value_get_length(sentry__scope_ref_tags, scope) == 0); sentry_value_decref(observer_data.tags); sentry_scope_free(scope); @@ -2455,7 +2494,7 @@ SENTRY_TEST(scope_set_attribute_invalid_decref_value) sentry_value_decref(no_type); TEST_CHECK_INT_EQUAL( - sentry_value_get_length(sentry__scope_get_attributes(scope)), 0); + scope_value_get_length(sentry__scope_ref_attributes, scope), 0); sentry_scope_free(scope); } @@ -2473,7 +2512,7 @@ SENTRY_TEST(scope_set_attribute_null_key_decref_value) sentry_value_decref(v); TEST_CHECK_INT_EQUAL( - sentry_value_get_length(sentry__scope_get_attributes(scope)), 0); + scope_value_get_length(sentry__scope_ref_attributes, scope), 0); sentry_scope_free(scope); } @@ -2517,9 +2556,10 @@ SENTRY_TEST(scope_clone_independence) TEST_CHECK(!sentry__scope_is_one_shot(clone)); // The clone carries over the source's data. - TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - sentry__scope_get_tags(clone), "shared")), - "original"); + sentry_value_t clone_tag + = scope_value_get_by_key(sentry__scope_ref_tags, clone, "shared"); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(clone_tag), "original"); + sentry_value_decref(clone_tag); TEST_CHECK(sentry__scope_get_level(clone) == SENTRY_LEVEL_WARNING); TEST_CHECK_INT_EQUAL(scope_breadcrumb_count(clone), 1); @@ -2529,11 +2569,14 @@ SENTRY_TEST(scope_clone_independence) sentry_scope_add_breadcrumb( clone, sentry_value_new_breadcrumb(NULL, "second")); - TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - sentry__scope_get_tags(scope), "shared")), - "original"); - TEST_CHECK(sentry_value_is_null( - sentry_value_get_by_key(sentry__scope_get_tags(scope), "clone_only"))); + sentry_value_t scope_tag + = scope_value_get_by_key(sentry__scope_ref_tags, scope, "shared"); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(scope_tag), "original"); + sentry_value_decref(scope_tag); + sentry_value_t clone_only + = scope_value_get_by_key(sentry__scope_ref_tags, scope, "clone_only"); + TEST_CHECK(sentry_value_is_null(clone_only)); + sentry_value_decref(clone_only); TEST_CHECK_INT_EQUAL(scope_breadcrumb_count(scope), 1); TEST_CHECK_INT_EQUAL(scope_breadcrumb_count(clone), 2); @@ -2568,30 +2611,33 @@ SENTRY_TEST(scope_clone_preserves_data) sentry_value_decref( sentry__scope_remove_attachment(scope, &attachment_id)); - TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - sentry__scope_get_tags(clone), "tag_key")), - "tag_value"); - TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - sentry__scope_get_contexts(clone), "device")), - "Xbox"); + sentry_value_t clone_tag + = scope_value_get_by_key(sentry__scope_ref_tags, clone, "tag_key"); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(clone_tag), "tag_value"); + sentry_value_decref(clone_tag); + sentry_value_t clone_context + = scope_value_get_by_key(sentry__scope_ref_contexts, clone, "device"); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(clone_context), "Xbox"); + sentry_value_decref(clone_context); sentry_value_t clone_user = sentry__scope_ref_user(clone); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(clone_user, "username")), "alice"); sentry_value_decref(clone_user); - TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - sentry__scope_get_extra(clone), "extra_key")), - "extra_value"); + sentry_value_t clone_extra + = scope_value_get_by_key(sentry__scope_ref_extra, clone, "extra_key"); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(clone_extra), "extra_value"); + sentry_value_decref(clone_extra); sentry_value_t clone_fingerprint = sentry__scope_ref_fingerprint(clone); TEST_CHECK_INT_EQUAL(sentry_value_get_length(clone_fingerprint), 2); sentry_value_decref(clone_fingerprint); TEST_CHECK(sentry__scope_get_level(clone) == SENTRY_LEVEL_WARNING); - TEST_CHECK_STRING_EQUAL( - sentry_value_as_string(sentry_value_get_by_key( - sentry_value_get_by_key( - sentry__scope_get_attributes(clone), "attr_key"), - "value")), + sentry_value_t clone_attribute = scope_value_get_by_key( + sentry__scope_ref_attributes, clone, "attr_key"); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( + clone_attribute, "value")), "attr_value"); + sentry_value_decref(clone_attribute); TEST_CHECK_INT_EQUAL(scope_breadcrumb_count(clone), 1); // Attachments are deep-copied into an independent list. @@ -2671,9 +2717,9 @@ SENTRY_TEST(scope_clear) sentry_scope_add_breadcrumb( scope, sentry_value_new_breadcrumb(NULL, "crumb")); - TEST_CHECK(sentry_value_get_length(sentry__scope_get_tags(scope)) == 1); + TEST_CHECK(scope_value_get_length(sentry__scope_ref_tags, scope) == 1); TEST_CHECK( - sentry_value_get_length(sentry__scope_get_attributes(scope)) == 1); + scope_value_get_length(sentry__scope_ref_attributes, scope) == 1); sentry_value_t scope_user = sentry__scope_ref_user(scope); TEST_CHECK(!sentry_value_is_null(scope_user)); sentry_value_decref(scope_user); @@ -2681,11 +2727,11 @@ SENTRY_TEST(scope_clear) sentry_scope_clear(scope); // Everything is reset to the state of a fresh scope. - TEST_CHECK(sentry_value_get_length(sentry__scope_get_tags(scope)) == 0); - TEST_CHECK(sentry_value_get_length(sentry__scope_get_extra(scope)) == 0); - TEST_CHECK(sentry_value_get_length(sentry__scope_get_contexts(scope)) == 0); + TEST_CHECK(scope_value_get_length(sentry__scope_ref_tags, scope) == 0); + TEST_CHECK(scope_value_get_length(sentry__scope_ref_extra, scope) == 0); + TEST_CHECK(scope_value_get_length(sentry__scope_ref_contexts, scope) == 0); TEST_CHECK( - sentry_value_get_length(sentry__scope_get_attributes(scope)) == 0); + scope_value_get_length(sentry__scope_ref_attributes, scope) == 0); scope_user = sentry__scope_ref_user(scope); TEST_CHECK(sentry_value_is_null(scope_user)); sentry_value_decref(scope_user); @@ -2710,7 +2756,7 @@ SENTRY_TEST(scope_clear) // The cleared scope is still usable. sentry_scope_set_tag(scope, "after", "clear"); - TEST_CHECK(sentry_value_get_length(sentry__scope_get_tags(scope)) == 1); + TEST_CHECK(scope_value_get_length(sentry__scope_ref_tags, scope) == 1); sentry_scope_free(scope); sentry_close(); @@ -2903,9 +2949,10 @@ SENTRY_TEST(scope_capture_user_owned) // The scope was applied but not freed, so reading and reusing it is safe // (a use-after-free here would trip the sanitizers). - TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - sentry__scope_get_tags(scope), "run")), - "first"); + sentry_value_t tag + = scope_value_get_by_key(sentry__scope_ref_tags, scope, "run"); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(tag), "first"); + sentry_value_decref(tag); sentry_scope_set_tag(scope, "run", "second"); sentry_scope_capture_event(scope, From 1a47d6b7daacd6a39b84d11c4c39ada70b29c5e9 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 7 Aug 2026 11:27:45 +0200 Subject: [PATCH 11/22] breadcrumbs --- src/backends/sentry_backend_crashpad.cpp | 3 +-- src/sentry_scope.c | 12 +++--------- src/sentry_scope.h | 3 +-- tests/unit/test_scope.c | 6 ++---- tests/unit/tests.inc | 2 +- 5 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index 1f99e1427c..714c68edc3 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -515,8 +515,7 @@ crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context) // written above and stays breadcrumb-free SENTRY_WITH_SCOPE (scope) { sentry_value_set_by_key(crash_event, "breadcrumbs", - sentry__ringbuffer_to_list( - sentry__scope_get_breadcrumbs(scope))); + sentry__scope_breadcrumbs_to_list(scope)); } sentry__session_replay_flush_pending( diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 8aaa1b36b4..58a65567ca 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -531,12 +531,6 @@ data_add_breadcrumb(sentry_scope_data_t *data, sentry_value_t breadcrumb) return added; } -static const sentry_ringbuffer_t * -data_get_breadcrumbs(const sentry_scope_data_t *data) -{ - return data->breadcrumbs; -} - static sentry_value_t data_ref_user(const sentry_scope_data_t *data) { @@ -1837,10 +1831,10 @@ sentry_scope_add_breadcrumb(sentry_scope_t *scope, sentry_value_t breadcrumb) } } -const sentry_ringbuffer_t * -sentry__scope_get_breadcrumbs(const sentry_scope_t *scope) +sentry_value_t +sentry__scope_breadcrumbs_to_list(const sentry_scope_t *scope) { - return data_get_breadcrumbs(scope->data); + return data_breadcrumbs_to_list(scope->data); } sentry_value_t diff --git a/src/sentry_scope.h b/src/sentry_scope.h index b9c7329c21..2eaaa00580 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -178,8 +178,7 @@ void sentry__scope_set_propagation_context( sentry_scope_t *scope, const char *key, sentry_value_t value); void sentry__scope_regenerate_propagation_context(sentry_scope_t *scope); -const sentry_ringbuffer_t *sentry__scope_get_breadcrumbs( - const sentry_scope_t *scope); +sentry_value_t sentry__scope_breadcrumbs_to_list(const sentry_scope_t *scope); sentry_transaction_t *sentry__scope_get_transaction_object( const sentry_scope_t *scope); diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 00901fcb8c..09deb9b4db 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -2533,8 +2533,7 @@ SENTRY_TEST(scope_ownership) static size_t scope_breadcrumb_count(const sentry_scope_t *scope) { - sentry_value_t breadcrumbs - = sentry__ringbuffer_to_list(sentry__scope_get_breadcrumbs(scope)); + sentry_value_t breadcrumbs = sentry__scope_breadcrumbs_to_list(scope); size_t count = sentry_value_get_length(breadcrumbs); sentry_value_decref(breadcrumbs); return count; @@ -2740,8 +2739,7 @@ SENTRY_TEST(scope_clear) TEST_CHECK(sentry_value_is_null(scope_fingerprint)); sentry_value_decref(scope_fingerprint); TEST_CHECK_INT_EQUAL(sentry__scope_get_level(scope), SENTRY_LEVEL_ERROR); - sentry_value_t crumbs - = sentry__ringbuffer_to_list(sentry__scope_get_breadcrumbs(scope)); + sentry_value_t crumbs = sentry__scope_breadcrumbs_to_list(scope); TEST_CHECK(sentry_value_get_length(crumbs) == 0); sentry_value_decref(crumbs); diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 2363e711c0..b5d2e8f1a8 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -505,8 +505,8 @@ XX(value_object_merge_nested) XX(value_object_merge_shallow) XX(value_object_merge_shallow_nested) XX(value_refcount) -XX(value_replace) XX(value_remove_by_null_key) +XX(value_replace) XX(value_set_by_null_key) XX(value_set_stacktrace) XX(value_string) From 7b7e3eff51751e9be4ee4ae88d6f2f516e32bf8a Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 7 Aug 2026 12:22:35 +0200 Subject: [PATCH 12/22] trace_context --- src/sentry_core.c | 17 ++++++++------ src/sentry_envelope.c | 6 +++-- src/sentry_scope.c | 49 +++++++++++++++++++++++++++++---------- src/sentry_scope.h | 4 +++- src/sentry_tracing.c | 3 ++- tests/unit/test_logs.c | 7 ++++-- tests/unit/test_tracing.c | 10 ++++---- 7 files changed, 67 insertions(+), 29 deletions(-) diff --git a/src/sentry_core.c b/src/sentry_core.c index 6d948a77e8..fb43fa8767 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -1354,9 +1354,11 @@ sentry_transaction_start_ts(sentry_transaction_context_t *opaque_tx_ctx, // captured outside this transaction also carry the new // trace_id, and align the tx's trace_id with it. sentry__scope_regenerate_propagation_context(scope); - sentry_value_t scope_trace_id = sentry_value_get_by_key( - sentry__scope_get_trace_context(scope), "trace_id"); - sentry_value_incref(scope_trace_id); + sentry_value_t trace_context + = sentry__scope_ref_trace_context(scope); + sentry_value_t scope_trace_id = sentry_value_incref( + sentry_value_get_by_key(trace_context, "trace_id")); + sentry_value_decref(trace_context); sentry_value_set_by_key(tx, "trace_id", scope_trace_id); sentry_value_remove_by_key(tx, "parent_span_id"); sentry_value_remove_by_key(tx, "sampled"); @@ -1369,8 +1371,10 @@ sentry_transaction_start_ts(sentry_transaction_context_t *opaque_tx_ctx, double sample_rand = 1.0; SENTRY_WITH_SCOPE (scope) { - sample_rand = sentry_value_as_double(sentry_value_get_by_key( - sentry__scope_get_trace_context(scope), "sample_rand")); + sentry_value_t trace_context = sentry__scope_ref_trace_context(scope); + sample_rand = sentry_value_as_double( + sentry_value_get_by_key(trace_context, "sample_rand")); + sentry_value_decref(trace_context); } sentry_sampling_context_t sampling_ctx = { opaque_tx_ctx, custom_sampling_ctx, NULL, sample_rand }; @@ -1442,8 +1446,7 @@ sentry__transaction_finish_value( = sentry_value_get_by_key(tx, "trace_id"); sentry_value_incref(txn_trace_id); - sentry_value_set_by_key(sentry__scope_get_trace_context(scope), - "trace_id", txn_trace_id); + sentry__scope_set_trace_context(scope, "trace_id", txn_trace_id); } } // The sampling decision should already be made for transactions diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index 672e38deeb..a30f4f80a4 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -416,8 +416,10 @@ sentry__envelope_add_event(sentry_envelope_t *envelope, sentry_value_t event) double sample_rand = (double)NAN; SENTRY_WITH_SCOPE (scope) { dsc = sentry__value_clone(sentry__scope_get_dsc(scope)); - sample_rand = sentry_value_as_double(sentry_value_get_by_key( - sentry__scope_get_trace_context(scope), "sample_rand")); + sentry_value_t trace_context = sentry__scope_ref_trace_context(scope); + sample_rand = sentry_value_as_double( + sentry_value_get_by_key(trace_context, "sample_rand")); + sentry_value_decref(trace_context); } if (!sentry_value_is_null(dsc)) { sentry_value_t trace_id = sentry_value_get_by_key( diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 58a65567ca..7a3116ae18 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -359,12 +359,6 @@ data_ref_propagation_context(const sentry_scope_data_t *data) return propagation_context; } -static sentry_value_t -data_get_trace_context(const sentry_scope_data_t *data) -{ - return sentry_value_get_by_key(data->propagation_context, "trace"); -} - static void data_set_propagation_context( sentry_scope_data_t *data, const char *key, sentry_value_t value) @@ -384,6 +378,30 @@ data_regenerate_propagation_context(sentry_scope_data_t *data) } } +static sentry_value_t +data_ref_trace_context(const sentry_scope_data_t *data) +{ + sentry_value_t trace_context = sentry_value_new_null(); + DATA_READ_LOCK(data) + { + trace_context = sentry_value_incref( + sentry_value_get_by_key(data->propagation_context, "trace")); + } + return trace_context; +} + +static void +data_set_trace_context( + sentry_scope_data_t *data, const char *key, sentry_value_t value) +{ + DATA_WRITE_LOCK(data) + { + sentry_value_set_by_key( + sentry_value_get_by_key(data->propagation_context, "trace"), key, + value); + } +} + static bool data_is_trace_managed(const sentry_scope_data_t *data) { @@ -1472,12 +1490,6 @@ sentry__scope_get_propagation_context(const sentry_scope_t *scope) return data_get_propagation_context(scope->data); } -sentry_value_t -sentry__scope_get_trace_context(const sentry_scope_t *scope) -{ - return data_get_trace_context(scope->data); -} - void sentry__scope_set_propagation_context( sentry_scope_t *scope, const char *key, sentry_value_t value) @@ -1491,6 +1503,19 @@ sentry__scope_regenerate_propagation_context(sentry_scope_t *scope) data_regenerate_propagation_context(scope->data); } +sentry_value_t +sentry__scope_ref_trace_context(const sentry_scope_t *scope) +{ + return data_ref_trace_context(scope->data); +} + +void +sentry__scope_set_trace_context( + sentry_scope_t *scope, const char *key, sentry_value_t value) +{ + data_set_trace_context(scope->data, key, value); +} + bool sentry__scope_is_trace_managed(const sentry_scope_t *scope) { diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 2eaaa00580..8d595c368f 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -173,10 +173,12 @@ void sentry__scope_remove_context_n( sentry_value_t sentry__scope_get_propagation_context( const sentry_scope_t *scope); -sentry_value_t sentry__scope_get_trace_context(const sentry_scope_t *scope); void sentry__scope_set_propagation_context( sentry_scope_t *scope, const char *key, sentry_value_t value); void sentry__scope_regenerate_propagation_context(sentry_scope_t *scope); +sentry_value_t sentry__scope_ref_trace_context(const sentry_scope_t *scope); +void sentry__scope_set_trace_context( + sentry_scope_t *scope, const char *key, sentry_value_t value); sentry_value_t sentry__scope_breadcrumbs_to_list(const sentry_scope_t *scope); diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c index a812942208..ffe5d64733 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -96,7 +96,7 @@ transaction_context_new_n(sentry_slice_t name, sentry_slice_t operation) sentry_value_new_string_n(name.ptr, name.len)); SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_t trace_context = sentry__scope_get_trace_context(scope); + sentry_value_t trace_context = sentry__scope_ref_trace_context(scope); if (!sentry__scope_is_trace_managed(scope) && !sentry_value_is_null(trace_context)) { // The trace is managed from outside, so we use the propagation @@ -109,6 +109,7 @@ transaction_context_new_n(sentry_slice_t name, sentry_slice_t operation) sentry__value_clone( sentry_value_get_by_key(trace_context, "parent_span_id"))); } + sentry_value_decref(trace_context); } return transaction_context; diff --git a/tests/unit/test_logs.c b/tests/unit/test_logs.c index 13182ff536..440cd637c4 100644 --- a/tests/unit/test_logs.c +++ b/tests/unit/test_logs.c @@ -765,10 +765,13 @@ SENTRY_TEST(scope_capture_log_trace_id) SENTRY_LOG_RETURN_SUCCESS); SENTRY_WITH_SCOPE (global_scope) { + sentry_value_t trace_context + = sentry__scope_ref_trace_context(global_scope); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( captured_log, "trace_id")), - sentry_value_as_string(sentry_value_get_by_key( - sentry__scope_get_trace_context(global_scope), "trace_id"))); + sentry_value_as_string( + sentry_value_get_by_key(trace_context, "trace_id"))); + sentry_value_decref(trace_context); } sentry_scope_free(scope); diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index 31fc613c28..fc7ad9a353 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -1566,7 +1566,7 @@ SENTRY_TEST(set_trace) SENTRY_WITH_SCOPE (scope) { sentry_value_t propagation_trace_context - = sentry__scope_get_trace_context(scope); + = sentry__scope_ref_trace_context(scope); TEST_CHECK(!sentry_value_is_null(propagation_trace_context)); CHECK_STRING_PROPERTY(propagation_trace_context, "type", "trace"); @@ -1579,6 +1579,7 @@ SENTRY_TEST(set_trace) sentry_value_get_by_key(propagation_trace_context, "span_id")); TEST_ASSERT(!!span_id); TEST_CHECK(strlen(span_id) > 0); + sentry_value_decref(propagation_trace_context); } sentry_close(); @@ -2426,11 +2427,12 @@ SENTRY_TEST(strict_continuation_no_baggage_forks) // Scope propagation follows the fork: no lingering upstream trace_id. SENTRY_WITH_SCOPE (scope) { - const char *scope_trace_id - = sentry_value_as_string(sentry_value_get_by_key( - sentry__scope_get_trace_context(scope), "trace_id")); + sentry_value_t trace_context = sentry__scope_ref_trace_context(scope); + const char *scope_trace_id = sentry_value_as_string( + sentry_value_get_by_key(trace_context, "trace_id")); TEST_CHECK(strcmp(scope_trace_id, UPSTREAM_TRACE_ID) != 0); TEST_CHECK_STRING_EQUAL(scope_trace_id, trace_id); + sentry_value_decref(trace_context); } sentry_transaction_finish(tx); From 75a20f320a349ad86d366e960c5d748d6466e49f Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 7 Aug 2026 12:27:26 +0200 Subject: [PATCH 13/22] propagation_context --- src/sentry_scope.c | 10 ++-------- src/sentry_scope.h | 2 +- tests/unit/test_scope.c | 8 +++++--- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 7a3116ae18..9ad5d389dc 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -342,12 +342,6 @@ clone_data(const sentry_scope_data_t *source) return clone; } -static sentry_value_t -data_get_propagation_context(const sentry_scope_data_t *data) -{ - return data->propagation_context; -} - static sentry_value_t data_ref_propagation_context(const sentry_scope_data_t *data) { @@ -1485,9 +1479,9 @@ sentry_scope_clone(const sentry_scope_t *scope) } sentry_value_t -sentry__scope_get_propagation_context(const sentry_scope_t *scope) +sentry__scope_ref_propagation_context(const sentry_scope_t *scope) { - return data_get_propagation_context(scope->data); + return data_ref_propagation_context(scope->data); } void diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 8d595c368f..3a46cd6479 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -171,7 +171,7 @@ void sentry__scope_remove_context(sentry_scope_t *scope, const char *key); void sentry__scope_remove_context_n( sentry_scope_t *scope, const char *key, size_t key_len); -sentry_value_t sentry__scope_get_propagation_context( +sentry_value_t sentry__scope_ref_propagation_context( const sentry_scope_t *scope); void sentry__scope_set_propagation_context( sentry_scope_t *scope, const char *key, sentry_value_t value); diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 09deb9b4db..c151a3d2c6 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -2744,10 +2744,12 @@ SENTRY_TEST(scope_clear) sentry_value_decref(crumbs); // ... except the trace, which is preserved. - TEST_CHECK_STRING_EQUAL( - sentry_value_as_string(sentry_value_get_by_key( - sentry__scope_get_propagation_context(scope), "marker")), + sentry_value_t propagation_context + = sentry__scope_ref_propagation_context(scope); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( + propagation_context, "marker")), "keep"); + sentry_value_decref(propagation_context); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( sentry__scope_get_dsc(scope), "marker")), "keep"); From f75d9cd7e4de445041d5e7c1095ea4733f7f674e Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 7 Aug 2026 12:36:22 +0200 Subject: [PATCH 14/22] dsc --- src/sentry_envelope.c | 4 +-- src/sentry_scope.c | 58 +++++++++++++++++++++++++++++++++------ src/sentry_scope.h | 6 +++- src/sentry_tracing.c | 3 +- tests/unit/test_scope.c | 12 +++++--- tests/unit/test_tracing.c | 12 +++++--- 6 files changed, 74 insertions(+), 21 deletions(-) diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index a30f4f80a4..d02d0e50a7 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -415,7 +415,7 @@ sentry__envelope_add_event(sentry_envelope_t *envelope, sentry_value_t event) sentry_value_t dsc = sentry_value_new_null(); double sample_rand = (double)NAN; SENTRY_WITH_SCOPE (scope) { - dsc = sentry__value_clone(sentry__scope_get_dsc(scope)); + dsc = sentry__scope_clone_dsc(scope); sentry_value_t trace_context = sentry__scope_ref_trace_context(scope); sample_rand = sentry_value_as_double( sentry_value_get_by_key(trace_context, "sample_rand")); @@ -497,7 +497,7 @@ sentry__envelope_add_transaction( sentry_value_t dsc = sentry_value_new_null(); SENTRY_WITH_SCOPE (scope) { - dsc = sentry__value_clone(sentry__scope_get_dsc(scope)); + dsc = sentry__scope_clone_dsc(scope); } if (!sentry_value_is_null(dsc)) { diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 9ad5d389dc..91aa981676 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -410,12 +410,6 @@ data_set_trace_managed(sentry_scope_data_t *data, bool managed) DATA_WRITE_LOCK(data) { data->trace_managed = managed; } } -static sentry_value_t -data_get_dsc(const sentry_scope_data_t *data) -{ - return data->dynamic_sampling_context; -} - static void data_freeze_dsc(sentry_scope_data_t *data, sentry_value_t incoming) { @@ -428,6 +422,40 @@ data_freeze_dsc(sentry_scope_data_t *data, sentry_value_t incoming) } } +static sentry_value_t +data_ref_dsc(const sentry_scope_data_t *data) +{ + sentry_value_t dsc = sentry_value_new_null(); + DATA_READ_LOCK(data) + { + dsc = sentry_value_incref(data->dynamic_sampling_context); + } + return dsc; +} + +static sentry_value_t +data_clone_dsc(const sentry_scope_data_t *data) +{ + sentry_value_t dsc = sentry_value_new_null(); + DATA_READ_LOCK(data) + { + dsc = sentry__value_clone(data->dynamic_sampling_context); + } + return dsc; +} + +static void +data_foreach_dsc(const sentry_scope_data_t *data, + void (*callback)(const char *key, sentry_value_t value, void *userdata), + void *userdata) +{ + DATA_READ_LOCK(data) + { + sentry__value_foreach_key_value( + data->dynamic_sampling_context, callback, userdata); + } +} + static void data_update_dsc(sentry_scope_data_t *data, const sentry_options_t *options) { @@ -1523,9 +1551,23 @@ sentry__scope_set_trace_managed(sentry_scope_t *scope, bool managed) } sentry_value_t -sentry__scope_get_dsc(const sentry_scope_t *scope) +sentry__scope_ref_dsc(const sentry_scope_t *scope) +{ + return data_ref_dsc(scope->data); +} + +sentry_value_t +sentry__scope_clone_dsc(const sentry_scope_t *scope) +{ + return data_clone_dsc(scope->data); +} + +void +sentry__scope_foreach_dsc(const sentry_scope_t *scope, + void (*callback)(const char *key, sentry_value_t value, void *userdata), + void *userdata) { - return data_get_dsc(scope->data); + data_foreach_dsc(scope->data, callback, userdata); } void diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 3a46cd6479..9859fd104c 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -261,7 +261,11 @@ void sentry__scope_end_notify(sentry_scope_t *scope); sentry__scope_end_notify(scope); \ } while (0) -sentry_value_t sentry__scope_get_dsc(const sentry_scope_t *scope); +sentry_value_t sentry__scope_ref_dsc(const sentry_scope_t *scope); +sentry_value_t sentry__scope_clone_dsc(const sentry_scope_t *scope); +void sentry__scope_foreach_dsc(const sentry_scope_t *scope, + void (*callback)(const char *key, sentry_value_t value, void *userdata), + void *userdata); /** * Rebuilds the scope's dynamic sampling context (DSC) from the SDK options diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c index ffe5d64733..4aba3a925e 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -961,8 +961,7 @@ sentry__span_iter_headers(sentry_value_t span, sentry__stringbuilder_append(&sb, sentry_value_as_string(trace_id)); SENTRY_WITH_SCOPE (scope) { - sentry__value_foreach_key_value( - sentry__scope_get_dsc(scope), append_baggage_member, &sb); + sentry__scope_foreach_dsc(scope, append_baggage_member, &sb); } char *baggage = sentry__stringbuilder_into_string(&sb); diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index c151a3d2c6..606be09dca 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -2698,8 +2698,10 @@ SENTRY_TEST(scope_clear) // dynamic sampling context. sentry__scope_set_propagation_context( scope, "marker", sentry_value_new_string("keep")); - sentry_value_set_by_key(sentry__scope_get_dsc(scope), "marker", - sentry_value_new_string("keep")); + sentry_value_t dsc = sentry_value_new_object(); + sentry_value_set_by_key(dsc, "marker", sentry_value_new_string("keep")); + sentry__scope_freeze_dsc(scope, dsc); + sentry_value_decref(dsc); sentry_scope_set_tag(scope, "tag", "value"); sentry_scope_set_extra(scope, "extra", sentry_value_new_string("value")); @@ -2750,9 +2752,11 @@ SENTRY_TEST(scope_clear) propagation_context, "marker")), "keep"); sentry_value_decref(propagation_context); - TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - sentry__scope_get_dsc(scope), "marker")), + sentry_value_t scope_dsc = sentry__scope_ref_dsc(scope); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(scope_dsc, "marker")), "keep"); + sentry_value_decref(scope_dsc); // The cleared scope is still usable. sentry_scope_set_tag(scope, "after", "clear"); diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index fc7ad9a353..320d8895af 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -2477,16 +2477,20 @@ SENTRY_TEST(set_trace_rebuilds_dsc_sample_rand) double init_sample_rand = 0.0; SENTRY_WITH_SCOPE (scope) { - init_sample_rand = sentry_value_as_double(sentry_value_get_by_key( - sentry__scope_get_dsc(scope), "sample_rand")); + sentry_value_t dsc = sentry__scope_ref_dsc(scope); + init_sample_rand = sentry_value_as_double( + sentry_value_get_by_key(dsc, "sample_rand")); + sentry_value_decref(dsc); } sentry_set_trace("11112222333344445555666677778888", "1234567812345678"); double new_sample_rand = -1.0; SENTRY_WITH_SCOPE (scope) { - new_sample_rand = sentry_value_as_double(sentry_value_get_by_key( - sentry__scope_get_dsc(scope), "sample_rand")); + sentry_value_t dsc = sentry__scope_ref_dsc(scope); + new_sample_rand = sentry_value_as_double( + sentry_value_get_by_key(dsc, "sample_rand")); + sentry_value_decref(dsc); } // sample_rand is regenerated for the new trace, so the DSC must reflect // the fresh value, not the init-time one. From a62469ae739503b9dd9d7645c183f0531fde5280 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 7 Aug 2026 13:10:37 +0200 Subject: [PATCH 15/22] span/transaction_object --- src/sentry_scope.c | 49 ++++++++++++------------ src/sentry_scope.h | 4 +- src/sentry_tracing.c | 10 +---- tests/unit/test_scope.c | 79 +++++++++++++++++++++++++++------------ tests/unit/test_tracing.c | 22 ++++++++--- 5 files changed, 102 insertions(+), 62 deletions(-) diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 91aa981676..bb4619108b 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -839,14 +839,18 @@ static sentry_uuid_t data_get_last_event_id(const sentry_scope_data_t *data) { sentry_uuid_t event_id = sentry_uuid_nil(); - DATA_READ_LOCK(data) { event_id = data->last_event_id; } + DATA_READ_LOCK (data) { + event_id = data->last_event_id; + } return event_id; } static void data_set_last_event_id(sentry_scope_data_t *data, sentry_uuid_t event_id) { - DATA_WRITE_LOCK(data) { data->last_event_id = event_id; } + DATA_WRITE_LOCK (data) { + data->last_event_id = event_id; + } } static sentry_value_t @@ -895,10 +899,14 @@ data_take_attachments(sentry_scope_data_t *data) } static sentry_transaction_t * -data_get_transaction_object(const sentry_scope_data_t *data) +data_ref_transaction_object(const sentry_scope_data_t *data) { sentry_transaction_t *transaction = NULL; - DATA_READ_LOCK(data) { transaction = data->transaction_object; } + DATA_READ_LOCK(data) + { + transaction = data->transaction_object; + sentry__transaction_incref(transaction); + } return transaction; } @@ -977,10 +985,14 @@ data_restore_transaction_object( } static sentry_span_t * -data_get_span(const sentry_scope_data_t *data) +data_ref_span(const sentry_scope_data_t *data) { sentry_span_t *span = NULL; - DATA_READ_LOCK(data) { span = data->span; } + DATA_READ_LOCK(data) + { + span = data->span; + sentry__span_incref(span); + } return span; } @@ -2258,23 +2270,13 @@ void sentry_scope_set_transaction_object( sentry_scope_t *scope, sentry_transaction_t *tx) { - sentry__span_decref(scope->span); - scope->span = NULL; - // incref before decref, so rebinding the same object cannot free it - sentry__transaction_incref(tx); - sentry__transaction_decref(scope->transaction_object); - scope->transaction_object = tx; + sentry__scope_set_transaction_object(scope, tx); } void sentry_scope_set_span(sentry_scope_t *scope, sentry_span_t *span) { - sentry__transaction_decref(scope->transaction_object); - scope->transaction_object = NULL; - // incref before decref, so rebinding the same object cannot free it - sentry__span_incref(span); - sentry__span_decref(scope->span); - scope->span = span; + sentry__scope_set_span(scope, span); } sentry_value_t @@ -2323,9 +2325,9 @@ sentry_scope_add_attachment(sentry_scope_t *scope, sentry_value_t attachment) } sentry_transaction_t * -sentry__scope_get_transaction_object(const sentry_scope_t *scope) +sentry__scope_ref_transaction_object(const sentry_scope_t *scope) { - return data_get_transaction_object(scope->data); + return data_ref_transaction_object(scope->data); } void @@ -2357,9 +2359,9 @@ sentry__scope_restore_transaction_object( } sentry_span_t * -sentry__scope_get_span(const sentry_scope_t *scope) +sentry__scope_ref_span(const sentry_scope_t *scope) { - return data_get_span(scope->data); + return data_ref_span(scope->data); } void @@ -2552,8 +2554,7 @@ sentry_scope_get_last_event_id(const sentry_scope_t *scope) } void -sentry__scope_set_last_event_id( - sentry_scope_t *scope, sentry_uuid_t event_id) +sentry__scope_set_last_event_id(sentry_scope_t *scope, sentry_uuid_t event_id) { if (scope) { data_set_last_event_id(scope->data, event_id); diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 9859fd104c..ba5fb1e339 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -182,7 +182,7 @@ void sentry__scope_set_trace_context( sentry_value_t sentry__scope_breadcrumbs_to_list(const sentry_scope_t *scope); -sentry_transaction_t *sentry__scope_get_transaction_object( +sentry_transaction_t *sentry__scope_ref_transaction_object( const sentry_scope_t *scope); void sentry__scope_set_transaction_object( sentry_scope_t *scope, sentry_transaction_t *transaction); @@ -193,7 +193,7 @@ bool sentry__scope_remove_transaction_value( bool sentry__scope_restore_transaction_object( sentry_scope_t *scope, sentry_transaction_t *transaction); -sentry_span_t *sentry__scope_get_span(const sentry_scope_t *scope); +sentry_span_t *sentry__scope_ref_span(const sentry_scope_t *scope); void sentry__scope_set_span(sentry_scope_t *scope, sentry_span_t *span); bool sentry__scope_remove_span(sentry_scope_t *scope, sentry_span_t *span); bool sentry__scope_remove_span_value( diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c index 4aba3a925e..e9efc7a5b6 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -1015,14 +1015,8 @@ save_active_trace(void) { saved_trace_t s = { 0 }; SENTRY_WITH_SCOPE (scope) { - s.saved_span = sentry__scope_get_span(scope); - if (s.saved_span) { - sentry__span_incref(s.saved_span); - } - s.saved_tx_obj = sentry__scope_get_transaction_object(scope); - if (s.saved_tx_obj) { - sentry__transaction_incref(s.saved_tx_obj); - } + s.saved_span = sentry__scope_ref_span(scope); + s.saved_tx_obj = sentry__scope_ref_transaction_object(scope); } s.active_tx = s.saved_span && s.saved_span->transaction ? s.saved_span->transaction diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 606be09dca..1a66b19e29 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -2671,13 +2671,17 @@ SENTRY_TEST(scope_clone_shares_span) sentry_scope_t *clone = NULL; sentry_transaction_t *scope_txn = NULL; SENTRY_WITH_SCOPE (scope) { - scope_txn = sentry__scope_get_transaction_object(scope); + scope_txn = sentry__scope_ref_transaction_object(scope); clone = sentry_scope_clone(scope); } // The active transaction is shared by reference, not dropped or duplicated. TEST_CHECK(scope_txn != NULL); - TEST_CHECK(sentry__scope_get_transaction_object(clone) == scope_txn); + sentry_transaction_t *clone_txn + = sentry__scope_ref_transaction_object(clone); + TEST_CHECK(clone_txn == scope_txn); + sentry__transaction_decref(clone_txn); + sentry__transaction_decref(scope_txn); // The shared reference keeps the transaction alive for the original: the // clone can be freed and the transaction still finished safely. @@ -3013,8 +3017,8 @@ SENTRY_TEST(scope_bind_transaction_object) // After unbinding, event falls back to the propagation context. TEST_ASSERT(!sentry_value_is_null(trace)); SENTRY_WITH_SCOPE (global_scope) { - sentry_value_t propagation_trace = sentry_value_get_by_key( - global_scope->propagation_context, "trace"); + sentry_value_t propagation_trace + = sentry__scope_ref_trace_context(global_scope); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(trace, "trace_id")), sentry_value_as_string( @@ -3023,6 +3027,7 @@ SENTRY_TEST(scope_bind_transaction_object) sentry_value_as_string(sentry_value_get_by_key(trace, "span_id")), sentry_value_as_string( sentry_value_get_by_key(propagation_trace, "span_id"))); + sentry_value_decref(propagation_trace); } sentry_value_decref(trace); @@ -3052,8 +3057,13 @@ SENTRY_TEST(scope_bind_span) // Binding a scope of our own leaves the global scope alone. SENTRY_WITH_SCOPE (global_scope) { - TEST_CHECK(global_scope->span == NULL); - TEST_CHECK(global_scope->transaction_object == NULL); + sentry_span_t *global_span = sentry__scope_ref_span(global_scope); + sentry_transaction_t *global_tx + = sentry__scope_ref_transaction_object(global_scope); + TEST_CHECK(global_span == NULL); + TEST_CHECK(global_tx == NULL); + sentry__span_decref(global_span); + sentry__transaction_decref(global_tx); } sentry_scope_capture_event(scope, @@ -3074,7 +3084,9 @@ SENTRY_TEST(scope_bind_span) // TODO: Finishing a span releases the caller's reference and only clears // the global scope. A user-owned scope still stamps that finished span onto // later events; this acknowledges the current behavior until it changes. - TEST_CHECK_PTR_EQUAL(scope->span, span); + sentry_span_t *bound_span = sentry__scope_ref_span(scope); + TEST_CHECK_PTR_EQUAL(bound_span, span); + sentry__span_decref(bound_span); sentry_value_decref(trace); sentry_scope_free(scope); @@ -3099,17 +3111,30 @@ SENTRY_TEST(scope_bind_span_or_transaction_not_both) sentry_scope_t *scope = sentry_scope_new(); sentry_scope_set_span(scope, span); sentry_scope_set_transaction_object(scope, tx); - TEST_CHECK(scope->span == NULL); - TEST_CHECK_PTR_EQUAL(scope->transaction_object, tx); + sentry_span_t *bound_span = sentry__scope_ref_span(scope); + sentry_transaction_t *bound_tx + = sentry__scope_ref_transaction_object(scope); + TEST_CHECK(bound_span == NULL); + TEST_CHECK_PTR_EQUAL(bound_tx, tx); + sentry__span_decref(bound_span); + sentry__transaction_decref(bound_tx); sentry_scope_set_span(scope, span); - TEST_CHECK(scope->transaction_object == NULL); - TEST_CHECK_PTR_EQUAL(scope->span, span); + bound_tx = sentry__scope_ref_transaction_object(scope); + bound_span = sentry__scope_ref_span(scope); + TEST_CHECK(bound_tx == NULL); + TEST_CHECK_PTR_EQUAL(bound_span, span); + sentry__transaction_decref(bound_tx); + sentry__span_decref(bound_span); // Passing null unbinds both. sentry_scope_set_transaction_object(scope, NULL); - TEST_CHECK(scope->span == NULL); - TEST_CHECK(scope->transaction_object == NULL); + bound_span = sentry__scope_ref_span(scope); + bound_tx = sentry__scope_ref_transaction_object(scope); + TEST_CHECK(bound_span == NULL); + TEST_CHECK(bound_tx == NULL); + sentry__span_decref(bound_span); + sentry__transaction_decref(bound_tx); sentry_scope_free(scope); sentry_span_finish(span); @@ -3138,21 +3163,25 @@ SENTRY_TEST(scope_rebind_same_object) // Rebinding what is already bound must not drop that last reference (a // use-after-free here would trip the sanitizers). - sentry_scope_set_span(scope, scope->span); - TEST_CHECK_PTR_EQUAL(scope->span, span); + sentry_scope_set_span(scope, span); + sentry_span_t *bound_span = sentry__scope_ref_span(scope); + TEST_CHECK_PTR_EQUAL(bound_span, span); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - scope->span->inner, "description")), + bound_span->inner, "description")), "select"); + sentry__span_decref(bound_span); sentry_scope_set_transaction_object(scope, tx); sentry_transaction_finish(tx); - sentry_scope_set_transaction_object(scope, scope->transaction_object); - TEST_CHECK_PTR_EQUAL(scope->transaction_object, tx); - TEST_CHECK_STRING_EQUAL( - sentry_value_as_string(sentry_value_get_by_key( - scope->transaction_object->inner, "transaction")), + sentry_scope_set_transaction_object(scope, tx); + sentry_transaction_t *bound_tx + = sentry__scope_ref_transaction_object(scope); + TEST_CHECK_PTR_EQUAL(bound_tx, tx); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( + bound_tx->inner, "transaction")), "txn"); + sentry__transaction_decref(bound_tx); sentry_scope_free(scope); @@ -3174,15 +3203,19 @@ SENTRY_TEST(scope_clone_keeps_bound_span) sentry_scope_t *scope = sentry_scope_new(); sentry_scope_set_span(scope, span); sentry_scope_t *clone = sentry_scope_clone(scope); - TEST_CHECK_PTR_EQUAL(clone->span, span); + sentry_span_t *clone_span = sentry__scope_ref_span(clone); + TEST_CHECK_PTR_EQUAL(clone_span, span); + sentry__span_decref(clone_span); // The clone owns its binding, so it outlives the original scope and caller // reference. sentry_scope_free(scope); sentry_span_finish(span); + clone_span = sentry__scope_ref_span(clone); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - clone->span->inner, "description")), + clone_span->inner, "description")), "select"); + sentry__span_decref(clone_span); sentry_scope_free(clone); sentry_transaction_finish(tx); diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index 320d8895af..be4ef66ec5 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -852,7 +852,9 @@ SENTRY_TEST(trace_finish) // Scope still points at the (finished) span so a subsequent crash event // inherits its trace context. SENTRY_WITH_SCOPE (scope) { - TEST_CHECK(sentry__scope_get_span(scope) != NULL); + sentry_span_t *scope_span = sentry__scope_ref_span(scope); + TEST_CHECK(scope_span != NULL); + sentry__span_decref(scope_span); } sentry__span_decref(grand); @@ -934,13 +936,19 @@ SENTRY_TEST(discard_transaction) sentry_set_transaction_object(tx); SENTRY_WITH_SCOPE (scope) { - TEST_CHECK(sentry__scope_get_transaction_object(scope) == tx); + sentry_transaction_t *scope_tx + = sentry__scope_ref_transaction_object(scope); + TEST_CHECK(scope_tx == tx); + sentry__transaction_decref(scope_tx); } sentry_transaction_discard(tx); SENTRY_WITH_SCOPE (scope) { - TEST_CHECK(sentry__scope_get_transaction_object(scope) == NULL); + sentry_transaction_t *scope_tx + = sentry__scope_ref_transaction_object(scope); + TEST_CHECK(scope_tx == NULL); + sentry__transaction_decref(scope_tx); } sentry_close(); @@ -970,13 +978,17 @@ SENTRY_TEST(discard_span) sentry_set_span(span); SENTRY_WITH_SCOPE (scope) { - TEST_CHECK(sentry__scope_get_span(scope) == span); + sentry_span_t *scope_span = sentry__scope_ref_span(scope); + TEST_CHECK(scope_span == span); + sentry__span_decref(scope_span); } sentry_span_discard(span); SENTRY_WITH_SCOPE (scope) { - TEST_CHECK(sentry__scope_get_span(scope) == NULL); + sentry_span_t *scope_span = sentry__scope_ref_span(scope); + TEST_CHECK(scope_span == NULL); + sentry__span_decref(scope_span); } TEST_CHECK_INT_EQUAL( sentry_value_get_length(sentry_value_get_by_key(tx->inner, "spans")), From 48a9d17533209b7ded8f127f9b56f58dfab7401a Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 7 Aug 2026 13:21:45 +0200 Subject: [PATCH 16/22] restore --- src/sentry_scope.c | 23 ++++-------------- src/sentry_scope.h | 2 +- tests/unit/test_scope.c | 50 +++++++++++++++++++++++++++++++++++++++ tests/unit/test_tracing.c | 25 +++++++++++++------- tests/unit/tests.inc | 1 + 5 files changed, 72 insertions(+), 29 deletions(-) diff --git a/src/sentry_scope.c b/src/sentry_scope.c index bb4619108b..186d62777e 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -976,7 +976,7 @@ data_restore_transaction_object( bool restored = false; DATA_WRITE_LOCK(data) { - if (!data->transaction_object && transaction) { + if (!data->transaction_object && !data->span && transaction) { data->transaction_object = transaction; restored = true; } @@ -1049,7 +1049,7 @@ data_restore_span(sentry_scope_data_t *data, sentry_span_t *span) bool restored = false; DATA_WRITE_LOCK(data) { - if (!data->span && span) { + if (!data->span && !data->transaction_object && span) { data->span = span; restored = true; } @@ -1698,27 +1698,12 @@ sentry__symbolize_stacktrace(sentry_value_t stacktrace) #endif #ifdef SENTRY_UNITTEST -static sentry_value_t -data_get_span_or_transaction(const sentry_scope_data_t *data) -{ - sentry_value_t value = sentry_value_new_null(); - DATA_READ_LOCK(data) - { - if (data->span) { - value = data->span->inner; - } else if (data->transaction_object) { - value = data->transaction_object->inner; - } - } - return value; -} - sentry_value_t -sentry__scope_get_span_or_transaction(void) +sentry__scope_ref_span_or_transaction(void) { sentry_value_t result = sentry_value_new_null(); SENTRY_WITH_SCOPE (scope) { - result = data_get_span_or_transaction(scope->data); + result = data_ref_span_or_transaction(scope->data); } return result; } diff --git a/src/sentry_scope.h b/src/sentry_scope.h index ba5fb1e339..cf092a660f 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -304,6 +304,6 @@ void sentry__scope_capture_envelope(sentry_scope_t *scope, // this is only used in unit tests #ifdef SENTRY_UNITTEST -sentry_value_t sentry__scope_get_span_or_transaction(void); +sentry_value_t sentry__scope_ref_span_or_transaction(void); bool sentry__scope_has_observers(const sentry_scope_t *scope); #endif diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 1a66b19e29..ea756e8f06 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -2691,6 +2691,56 @@ SENTRY_TEST(scope_clone_shares_span) sentry_close(); } +SENTRY_TEST(scope_restore_trace) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_init(options); + + sentry_scope_t *scope = sentry_scope_new(); + sentry_transaction_context_t *tx_ctx + = sentry_transaction_context_new("txn", NULL); + sentry_transaction_t *tx + = sentry_transaction_start(tx_ctx, sentry_value_new_null()); + sentry_span_t *span = sentry_transaction_start_child(tx, "op", NULL); + + sentry__scope_set_transaction_object(scope, tx); + TEST_CHECK(!sentry__scope_restore_span(scope, span)); + + sentry_span_t *scope_span = sentry__scope_ref_span(scope); + TEST_CHECK(scope_span == NULL); + sentry__span_decref(scope_span); + sentry_transaction_t *scope_tx + = sentry__scope_ref_transaction_object(scope); + TEST_CHECK(scope_tx == tx); + sentry__transaction_decref(scope_tx); + + sentry_scope_free(scope); + sentry__span_decref(span); + sentry__transaction_decref(tx); + + scope = sentry_scope_new(); + tx_ctx = sentry_transaction_context_new("txn", NULL); + tx = sentry_transaction_start(tx_ctx, sentry_value_new_null()); + span = sentry_transaction_start_child(tx, "op", NULL); + + sentry__scope_set_span(scope, span); + TEST_CHECK(!sentry__scope_restore_transaction_object(scope, tx)); + + scope_tx = sentry__scope_ref_transaction_object(scope); + TEST_CHECK(scope_tx == NULL); + sentry__transaction_decref(scope_tx); + scope_span = sentry__scope_ref_span(scope); + TEST_CHECK(scope_span == span); + sentry__span_decref(scope_span); + + sentry_scope_free(scope); + sentry__span_decref(span); + sentry__transaction_decref(tx); + + sentry_close(); +} + SENTRY_TEST(scope_clear) { SENTRY_TEST_OPTIONS_NEW(options); diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index be4ef66ec5..02e2a6197f 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -420,12 +420,14 @@ SENTRY_TEST(multiple_transactions) = sentry_transaction_start(tx_ctx, sentry_value_new_null()); sentry_set_transaction_object(tx); - sentry_value_t scope_tx = sentry__scope_get_span_or_transaction(); + sentry_value_t scope_tx = sentry__scope_ref_span_or_transaction(); CHECK_STRING_PROPERTY(scope_tx, "transaction", "wow!"); + sentry_value_decref(scope_tx); sentry_uuid_t event_id = sentry_transaction_finish(tx); - scope_tx = sentry__scope_get_span_or_transaction(); + scope_tx = sentry__scope_ref_span_or_transaction(); TEST_CHECK(sentry_value_is_null(scope_tx)); + sentry_value_decref(scope_tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); // Set transaction on scope twice, back-to-back without finishing the first @@ -437,8 +439,9 @@ SENTRY_TEST(multiple_transactions) tx_ctx = sentry_transaction_context_new("wowee!", NULL); tx = sentry_transaction_start(tx_ctx, sentry_value_new_null()); sentry_set_transaction_object(tx); - scope_tx = sentry__scope_get_span_or_transaction(); + scope_tx = sentry__scope_ref_span_or_transaction(); CHECK_STRING_PROPERTY(scope_tx, "transaction", "wowee!"); + sentry_value_decref(scope_tx); event_id = sentry_transaction_finish(tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); @@ -520,20 +523,21 @@ SENTRY_TEST(spans_on_scope) // Peek into the transaction's span list and make sure everything is // good - sentry_value_t scope_tx = sentry__scope_get_span_or_transaction(); - const char *trace_id - = sentry_value_as_string(sentry_value_get_by_key(scope_tx, "trace_id")); - const char *parent_span_id - = sentry_value_as_string(sentry_value_get_by_key(scope_tx, "span_id")); + sentry_value_t scope_tx = sentry__scope_ref_span_or_transaction(); + char *trace_id = sentry__string_clone( + sentry_value_as_string(sentry_value_get_by_key(scope_tx, "trace_id"))); + char *parent_span_id = sentry__string_clone( + sentry_value_as_string(sentry_value_get_by_key(scope_tx, "span_id"))); // Don't track the span yet TEST_CHECK(IS_NULL(scope_tx, "spans")); + sentry_value_decref(scope_tx); // Sanity check that child isn't finished yet TEST_CHECK(IS_NULL(child, "timestamp")); sentry_span_finish(opaque_child); - scope_tx = sentry__scope_get_span_or_transaction(); + scope_tx = sentry__scope_ref_span_or_transaction(); TEST_CHECK(!IS_NULL(scope_tx, "spans")); sentry_value_t spans = sentry_value_get_by_key(scope_tx, "spans"); TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 1); @@ -546,6 +550,9 @@ SENTRY_TEST(spans_on_scope) CHECK_STRING_PROPERTY(stored_child, "description", "goose"); // Should be finished TEST_CHECK(!IS_NULL(stored_child, "timestamp")); + sentry_value_decref(scope_tx); + sentry_free(trace_id); + sentry_free(parent_span_id); sentry__transaction_decref(opaque_tx); diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index b5d2e8f1a8..f2cc1bba35 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -363,6 +363,7 @@ XX(scope_ownership) XX(scope_propagation_context) XX(scope_rebind_same_object) XX(scope_remove_fingerprint_capture) +XX(scope_restore_trace) XX(scope_set_attribute_invalid_decref_value) XX(scope_set_attribute_null_key_decref_value) XX(scope_tags) From c071c114d3fc00da9335e08254ea3793bfbaabc1 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 7 Aug 2026 16:18:42 +0200 Subject: [PATCH 17/22] lock observers --- src/sentry_scope.c | 40 ++++++++++++++++++++++++++++++++++++---- src/sentry_scope.h | 11 ++++++----- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 186d62777e..269c2f2294 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -1207,6 +1207,7 @@ init_scope(sentry_scope_t *scope, sentry_scope_data_t *data) scope->num_observers = 0; scope->is_notifying = 0; scope->pending_flush = false; + sentry__mutex_init(&scope->observers_lock); scope->one_shot = false; return true; } @@ -1235,13 +1236,16 @@ get_scope(void) static void cleanup_observers(sentry_scope_t *scope) { + sentry__mutex_lock(&scope->observers_lock); for (size_t i = 0; i < scope->num_observers; i++) { sentry_free(scope->observers[i]); } sentry_free(scope->observers); scope->observers = NULL; scope->num_observers = 0; + scope->is_notifying = 0; scope->pending_flush = false; + sentry__mutex_unlock(&scope->observers_lock); } static void @@ -1250,6 +1254,7 @@ cleanup_scope(sentry_scope_t *scope) free_data(scope->data); scope->data = NULL; cleanup_observers(scope); + sentry__mutex_free(&scope->observers_lock); } void @@ -1262,6 +1267,7 @@ sentry__scope_cleanup(void) cleanup_global_data(g_scope.data); g_scope.data = NULL; cleanup_observers(&g_scope); + sentry__mutex_free(&g_scope.observers_lock); } sentry__mutex_unlock(&g_lock); } @@ -1279,6 +1285,7 @@ unlock_scope(bool flush) { SENTRY__MUTEX_INIT_DYN_ONCE(g_lock); + sentry__mutex_lock(&g_scope.observers_lock); if (g_scope.is_notifying > 0) { // defer the flush requested by a reentrant scope change g_scope.pending_flush = flush || g_scope.pending_flush; @@ -1288,6 +1295,7 @@ unlock_scope(bool flush) flush = flush || g_scope.pending_flush; g_scope.pending_flush = false; } + sentry__mutex_unlock(&g_scope.observers_lock); // we try to unlock the scope as soon as possible. The // backend will do its own `WITH_SCOPE` internally. @@ -1327,10 +1335,12 @@ sentry__scope_add_observer( return false; } + sentry__mutex_lock(&scope->observers_lock); size_t new_count = scope->num_observers + 1; sentry_scope_observer_t **new_array = sentry__calloc(new_count, sizeof(sentry_scope_observer_t *)); if (!new_array) { + sentry__mutex_unlock(&scope->observers_lock); sentry_free(observer); return false; } @@ -1342,6 +1352,7 @@ sentry__scope_add_observer( new_array[scope->num_observers] = observer; scope->observers = new_array; scope->num_observers = new_count; + sentry__mutex_unlock(&scope->observers_lock); return true; } @@ -1349,7 +1360,13 @@ void sentry__scope_remove_observer( sentry_scope_t *scope, sentry_scope_observer_t *observer) { - if (!observer || !scope->observers) { + if (!observer) { + return; + } + + sentry__mutex_lock(&scope->observers_lock); + if (!scope->observers) { + sentry__mutex_unlock(&scope->observers_lock); return; } @@ -1362,6 +1379,7 @@ sentry__scope_remove_observer( if (scope->is_notifying) { // avoid shifting the array while SENTRY_SCOPE_NOTIFY is iterating scope->observers[i] = NULL; + sentry__mutex_unlock(&scope->observers_lock); return; } for (size_t j = i + 1; j < scope->num_observers; j++) { @@ -1372,13 +1390,16 @@ sentry__scope_remove_observer( sentry_free(scope->observers); scope->observers = NULL; } + sentry__mutex_unlock(&scope->observers_lock); return; } + sentry__mutex_unlock(&scope->observers_lock); } size_t sentry__scope_begin_notify(sentry_scope_t *scope) { + sentry__mutex_lock(&scope->observers_lock); scope->is_notifying++; return scope->num_observers; } @@ -1387,9 +1408,11 @@ void sentry__scope_end_notify(sentry_scope_t *scope) { if (--scope->is_notifying > 0) { + sentry__mutex_unlock(&scope->observers_lock); return; } if (!scope->observers) { + sentry__mutex_unlock(&scope->observers_lock); return; } @@ -1406,6 +1429,7 @@ sentry__scope_end_notify(sentry_scope_t *scope) sentry_free(scope->observers); scope->observers = NULL; } + sentry__mutex_unlock(&scope->observers_lock); } sentry_scope_t * @@ -1509,8 +1533,13 @@ sentry_scope_clone(const sentry_scope_t *scope) return NULL; } - clone->data = clone_data(scope->data); - if (!clone->data) { + sentry_scope_data_t *data = clone_data(scope->data); + if (!data) { + sentry_free(clone); + return NULL; + } + if (!init_scope(clone, data)) { + free_data(data); sentry_free(clone); return NULL; } @@ -1711,7 +1740,10 @@ sentry__scope_ref_span_or_transaction(void) bool sentry__scope_has_observers(const sentry_scope_t *scope) { - return scope->num_observers > 0; + sentry__mutex_lock((sentry_mutex_t *)&scope->observers_lock); + bool has_observers = scope->num_observers > 0; + sentry__mutex_unlock((sentry_mutex_t *)&scope->observers_lock); + return has_observers; } #endif diff --git a/src/sentry_scope.h b/src/sentry_scope.h index cf092a660f..9b912030be 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -13,7 +13,7 @@ * Scope observer — one callback per scope property. * * Implementors set the function pointers they care about. NULL pointers are - * skipped. Callbacks are invoked while the scope lock is held. + * skipped. Callbacks are invoked while the scope observer lock is held. * The data pointer is passed as the first argument to each callback. * * Note: callback arguments are borrowed and valid only for the duration of the @@ -58,6 +58,7 @@ typedef struct sentry_scope_data_s sentry_scope_data_t; struct sentry_scope_s { sentry_scope_data_t *data; + sentry_mutex_t observers_lock; sentry_scope_observer_t **observers; size_t num_observers; size_t is_notifying; @@ -230,8 +231,8 @@ sentry_scope_observer_t *sentry__scope_observer_new(void); * Register a scope observer. * * Takes ownership of `observer`; the caller must not free it after this call. - * Must be called while holding the scope lock. Registration order is respected - * — observers are notified in registration order. + * Registration order is respected: observers are notified in registration + * order. */ bool sentry__scope_add_observer( sentry_scope_t *scope, sentry_scope_observer_t *observer); @@ -239,8 +240,8 @@ bool sentry__scope_add_observer( /** * Remove a scope observer. * - * Frees `observer` if it is registered. Must be called while holding the scope - * lock. Does nothing if `observer` is NULL or not registered. + * Frees `observer` if it is registered. Does nothing if `observer` is NULL or + * not registered. */ void sentry__scope_remove_observer( sentry_scope_t *scope, sentry_scope_observer_t *observer); From dd2c941f2dfda10c7528d4c9dd4a5d974d9c57e3 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 7 Aug 2026 16:40:09 +0200 Subject: [PATCH 18/22] finally :D --- src/sentry_scope.c | 35 ++++++++++++++++++++++++++++------- src/sentry_scope.h | 26 ++++++++++++++++++-------- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 269c2f2294..dfa0fa1a4e 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -1280,10 +1280,22 @@ sentry__scope_lock(void) return get_scope(); } -static void -unlock_scope(bool flush) +sentry_scope_t * +sentry__scope_begin(void) { SENTRY__MUTEX_INIT_DYN_ONCE(g_lock); + sentry__mutex_lock(&g_lock); + sentry_scope_t *scope = get_scope(); + sentry__mutex_unlock(&g_lock); + return scope; +} + +static void +finish_scope(bool flush, bool unlock) +{ + if (unlock) { + SENTRY__MUTEX_INIT_DYN_ONCE(g_lock); + } sentry__mutex_lock(&g_scope.observers_lock); if (g_scope.is_notifying > 0) { @@ -1297,9 +1309,12 @@ unlock_scope(bool flush) } sentry__mutex_unlock(&g_scope.observers_lock); - // we try to unlock the scope as soon as possible. The - // backend will do its own `WITH_SCOPE` internally. - sentry__mutex_unlock(&g_lock); + if (unlock) { + // we try to unlock the scope as soon as possible. The + // backend will do its own `WITH_SCOPE` internally. + sentry__mutex_unlock(&g_lock); + } + if (flush) { SENTRY_WITH_OPTIONS (options) { if (options->backend && options->backend->flush_scope_func) { @@ -1312,13 +1327,19 @@ unlock_scope(bool flush) void sentry__scope_unlock(void) { - unlock_scope(false); + finish_scope(false, true); } void sentry__scope_flush_unlock(void) { - unlock_scope(true); + finish_scope(true, true); +} + +void +sentry__scope_end(bool flush) +{ + finish_scope(flush, false); } sentry_scope_observer_t * diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 9b912030be..d2caee46be 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -90,6 +90,11 @@ typedef enum { */ sentry_scope_t *sentry__scope_lock(void); +/** + * This will return the global scope, initializing it if needed. + */ +sentry_scope_t *sentry__scope_begin(void); + /** * Release the lock on the global scope. */ @@ -118,6 +123,12 @@ void sentry__scope_free_one_shot(sentry_scope_t *scope); */ void sentry__scope_flush_unlock(void); +/** + * This will finish a global scope access, optionally notifying any backend of + * scope changes. + */ +void sentry__scope_end(bool flush); + /** * This will merge the requested data which is in the given `scope` to the given * `event`. @@ -205,18 +216,17 @@ bool sentry__scope_is_trace_managed(const sentry_scope_t *scope); void sentry__scope_set_trace_managed(sentry_scope_t *scope, bool managed); /** - * These are convenience macros to automatically lock/unlock the global scope - * inside a code block. + * These are convenience macros to access the global scope inside a code block. */ #define SENTRY_WITH_SCOPE(Scope) \ - for (const sentry_scope_t *Scope = sentry__scope_lock(); Scope; \ - sentry__scope_unlock(), Scope = NULL) + for (const sentry_scope_t *Scope = sentry__scope_begin(); Scope; \ + sentry__scope_end(false), Scope = NULL) #define SENTRY_WITH_SCOPE_MUT(Scope) \ - for (sentry_scope_t *Scope = sentry__scope_lock(); Scope; \ - sentry__scope_flush_unlock(), Scope = NULL) + for (sentry_scope_t *Scope = sentry__scope_begin(); Scope; \ + sentry__scope_end(true), Scope = NULL) #define SENTRY_WITH_SCOPE_MUT_NO_FLUSH(Scope) \ - for (sentry_scope_t *Scope = sentry__scope_lock(); Scope; \ - sentry__scope_unlock(), Scope = NULL) + for (sentry_scope_t *Scope = sentry__scope_begin(); Scope; \ + sentry__scope_end(false), Scope = NULL) /** * Allocate and zero-initialize a scope observer. From 570d293624e4480a285b3e9fd58398e0128fe4bf Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 7 Aug 2026 16:57:23 +0200 Subject: [PATCH 19/22] fix formatting --- .clang-format | 2 +- src/sentry_scope.c | 220 ++++++++++++++++++++------------------------- 2 files changed, 100 insertions(+), 122 deletions(-) diff --git a/.clang-format b/.clang-format index 84446160fd..b8f96ae156 100644 --- a/.clang-format +++ b/.clang-format @@ -4,5 +4,5 @@ IndentPPDirectives: AfterHash ColumnLimit: 80 AlwaysBreakAfterDefinitionReturnType: All PointerAlignment: Right -ForEachMacros: ['SENTRY_WITH_SCOPE', 'SENTRY_WITH_SCOPE_MUT', 'SENTRY_WITH_SCOPE_MUT_NO_FLUSH', 'SENTRY_WITH_OPTIONS', 'SENTRY_WITH_OPTIONS_MUT'] +ForEachMacros: ['SENTRY_WITH_SCOPE', 'SENTRY_WITH_SCOPE_MUT', 'SENTRY_WITH_SCOPE_MUT_NO_FLUSH', 'SENTRY_WITH_OPTIONS', 'SENTRY_WITH_OPTIONS_MUT', 'DATA_READ_LOCK', 'DATA_WRITE_LOCK'] InsertNewlineAtEOF: True diff --git a/src/sentry_scope.c b/src/sentry_scope.c index dfa0fa1a4e..4441c87ede 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -247,8 +247,7 @@ init_global_data(sentry_scope_data_t *data) static void data_apply_options(sentry_scope_data_t *data, sentry_options_t *options) { - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { if (options->sdk_name) { sentry_value_t sdk_name = sentry_value_new_string(options->sdk_name); @@ -281,8 +280,7 @@ data_apply_options(sentry_scope_data_t *data, sentry_options_t *options) static void clear_data(sentry_scope_data_t *data) { - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { bool trace_managed = data->trace_managed; sentry_value_t propagation_context = sentry_value_incref(data->propagation_context); @@ -309,8 +307,7 @@ clone_data(const sentry_scope_data_t *source) } sentry__rwlock_init(&clone->rwlock); - DATA_READ_LOCK(source) - { + DATA_READ_LOCK (source) { clone->release = sentry__value_clone(source->release); clone->environment = sentry__value_clone(source->environment); clone->transaction = sentry__value_clone(source->transaction); @@ -346,8 +343,7 @@ static sentry_value_t data_ref_propagation_context(const sentry_scope_data_t *data) { sentry_value_t propagation_context = sentry_value_new_null(); - DATA_READ_LOCK(data) - { + DATA_READ_LOCK (data) { propagation_context = sentry_value_incref(data->propagation_context); } return propagation_context; @@ -357,8 +353,7 @@ static void data_set_propagation_context( sentry_scope_data_t *data, const char *key, sentry_value_t value) { - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { sentry_value_set_by_key(data->propagation_context, key, value); } } @@ -366,8 +361,7 @@ data_set_propagation_context( static void data_regenerate_propagation_context(sentry_scope_data_t *data) { - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { generate_propagation_context(data->propagation_context); } } @@ -376,8 +370,7 @@ static sentry_value_t data_ref_trace_context(const sentry_scope_data_t *data) { sentry_value_t trace_context = sentry_value_new_null(); - DATA_READ_LOCK(data) - { + DATA_READ_LOCK (data) { trace_context = sentry_value_incref( sentry_value_get_by_key(data->propagation_context, "trace")); } @@ -388,8 +381,7 @@ static void data_set_trace_context( sentry_scope_data_t *data, const char *key, sentry_value_t value) { - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { sentry_value_set_by_key( sentry_value_get_by_key(data->propagation_context, "trace"), key, value); @@ -400,14 +392,18 @@ static bool data_is_trace_managed(const sentry_scope_data_t *data) { bool managed = false; - DATA_READ_LOCK(data) { managed = data->trace_managed; } + DATA_READ_LOCK (data) { + managed = data->trace_managed; + } return managed; } static void data_set_trace_managed(sentry_scope_data_t *data, bool managed) { - DATA_WRITE_LOCK(data) { data->trace_managed = managed; } + DATA_WRITE_LOCK (data) { + data->trace_managed = managed; + } } static void @@ -416,8 +412,7 @@ data_freeze_dsc(sentry_scope_data_t *data, sentry_value_t incoming) sentry_value_t dsc = sentry_value_new_object(); sentry__value_merge_objects(dsc, incoming); sentry_value_freeze(dsc); - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { sentry__value_replace(&data->dynamic_sampling_context, dsc); } } @@ -426,8 +421,7 @@ static sentry_value_t data_ref_dsc(const sentry_scope_data_t *data) { sentry_value_t dsc = sentry_value_new_null(); - DATA_READ_LOCK(data) - { + DATA_READ_LOCK (data) { dsc = sentry_value_incref(data->dynamic_sampling_context); } return dsc; @@ -437,8 +431,7 @@ static sentry_value_t data_clone_dsc(const sentry_scope_data_t *data) { sentry_value_t dsc = sentry_value_new_null(); - DATA_READ_LOCK(data) - { + DATA_READ_LOCK (data) { dsc = sentry__value_clone(data->dynamic_sampling_context); } return dsc; @@ -449,8 +442,7 @@ data_foreach_dsc(const sentry_scope_data_t *data, void (*callback)(const char *key, sentry_value_t value, void *userdata), void *userdata) { - DATA_READ_LOCK(data) - { + DATA_READ_LOCK (data) { sentry__value_foreach_key_value( data->dynamic_sampling_context, callback, userdata); } @@ -476,8 +468,7 @@ data_update_dsc(sentry_scope_data_t *data, const sentry_options_t *options) dsc, "sample_rate", sentry_value_new_double(1.0)); } - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { sentry_value_t sample_rand = sentry_value_get_by_key( sentry_value_get_by_key(data->propagation_context, "trace"), "sample_rand"); @@ -495,15 +486,16 @@ static sentry_value_t data_ref_client_sdk(const sentry_scope_data_t *data) { sentry_value_t client_sdk = sentry_value_new_null(); - DATA_READ_LOCK(data) { client_sdk = sentry_value_incref(data->client_sdk); } + DATA_READ_LOCK (data) { + client_sdk = sentry_value_incref(data->client_sdk); + } return client_sdk; } static void data_apply_tags_and_extra(const sentry_scope_data_t *data, sentry_value_t event) { - DATA_READ_LOCK(data) - { + DATA_READ_LOCK (data) { sentry_value_t event_tags = sentry_value_get_by_key(event, "tags"); if (sentry_value_is_null(event_tags)) { if (!sentry_value_is_null(data->tags)) { @@ -530,7 +522,9 @@ static sentry_value_t data_clone_contexts(const sentry_scope_data_t *data) { sentry_value_t contexts = sentry_value_new_null(); - DATA_READ_LOCK(data) { contexts = sentry__value_clone(data->contexts); } + DATA_READ_LOCK (data) { + contexts = sentry__value_clone(data->contexts); + } return contexts; } @@ -538,8 +532,7 @@ static sentry_value_t data_ref_span_or_transaction(const sentry_scope_data_t *data) { sentry_value_t value = sentry_value_new_null(); - DATA_READ_LOCK(data) - { + DATA_READ_LOCK (data) { if (data->span) { value = sentry_value_incref(data->span->inner); } else if (data->transaction_object) { @@ -553,8 +546,7 @@ static sentry_value_t data_breadcrumbs_to_list(const sentry_scope_data_t *data) { sentry_value_t breadcrumbs = sentry_value_new_null(); - DATA_READ_LOCK(data) - { + DATA_READ_LOCK (data) { breadcrumbs = sentry__ringbuffer_to_list(data->breadcrumbs); } return breadcrumbs; @@ -564,8 +556,7 @@ static bool data_add_breadcrumb(sentry_scope_data_t *data, sentry_value_t breadcrumb) { bool added = false; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { added = sentry__ringbuffer_append(data->breadcrumbs, breadcrumb) == 0; } return added; @@ -575,15 +566,16 @@ static sentry_value_t data_ref_user(const sentry_scope_data_t *data) { sentry_value_t user = sentry_value_new_null(); - DATA_READ_LOCK(data) { user = sentry_value_incref(data->user); } + DATA_READ_LOCK (data) { + user = sentry_value_incref(data->user); + } return user; } static void data_set_user(sentry_scope_data_t *data, sentry_value_t user) { - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { sentry__value_replace(&data->user, sentry_value_incref(user)); } } @@ -592,7 +584,9 @@ static sentry_value_t data_ref_tags(const sentry_scope_data_t *data) { sentry_value_t tags = sentry_value_new_null(); - DATA_READ_LOCK(data) { tags = sentry_value_incref(data->tags); } + DATA_READ_LOCK (data) { + tags = sentry_value_incref(data->tags); + } return tags; } @@ -600,8 +594,7 @@ static bool data_set_tag(sentry_scope_data_t *data, const char *key, sentry_value_t value) { bool did_set = false; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { did_set = sentry_value_set_by_key(data->tags, key, value) == 0; } return did_set; @@ -612,8 +605,7 @@ data_set_tag_n(sentry_scope_data_t *data, const char *key, size_t key_len, sentry_value_t value) { bool did_set = false; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { did_set = sentry_value_set_by_key_n(data->tags, key, key_len, value) == 0; } @@ -624,8 +616,7 @@ static bool data_remove_tag(sentry_scope_data_t *data, const char *key) { bool removed = false; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { removed = sentry_value_remove_by_key(data->tags, key) == 0; } return removed; @@ -635,8 +626,7 @@ static char * data_remove_tag_n(sentry_scope_data_t *data, const char *key, size_t key_len) { char *removed_key = NULL; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { removed_key = sentry__value_remove_and_take_key_n(data->tags, key, key_len); } @@ -647,7 +637,9 @@ static sentry_value_t data_ref_extra(const sentry_scope_data_t *data) { sentry_value_t extra = sentry_value_new_null(); - DATA_READ_LOCK(data) { extra = sentry_value_incref(data->extra); } + DATA_READ_LOCK (data) { + extra = sentry_value_incref(data->extra); + } return extra; } @@ -655,8 +647,7 @@ static bool data_set_extra(sentry_scope_data_t *data, const char *key, sentry_value_t value) { bool did_set = false; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { did_set = sentry_value_set_by_key(data->extra, key, value) == 0; } return did_set; @@ -667,8 +658,7 @@ data_set_extra_n(sentry_scope_data_t *data, const char *key, size_t key_len, sentry_value_t value) { bool did_set = false; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { did_set = sentry_value_set_by_key_n(data->extra, key, key_len, value) == 0; } @@ -679,8 +669,7 @@ static bool data_remove_extra(sentry_scope_data_t *data, const char *key) { bool removed = false; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { removed = sentry_value_remove_by_key(data->extra, key) == 0; } return removed; @@ -690,8 +679,7 @@ static char * data_remove_extra_n(sentry_scope_data_t *data, const char *key, size_t key_len) { char *removed_key = NULL; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { removed_key = sentry__value_remove_and_take_key_n(data->extra, key, key_len); } @@ -702,7 +690,9 @@ static sentry_value_t data_ref_attributes(const sentry_scope_data_t *data) { sentry_value_t attributes = sentry_value_new_null(); - DATA_READ_LOCK(data) { attributes = sentry_value_incref(data->attributes); } + DATA_READ_LOCK (data) { + attributes = sentry_value_incref(data->attributes); + } return attributes; } @@ -711,8 +701,7 @@ data_set_attribute_n(sentry_scope_data_t *data, const char *key, size_t key_len, sentry_value_t attribute) { bool did_set = false; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { did_set = sentry_value_set_by_key_n( data->attributes, key, key_len, attribute) == 0; @@ -723,15 +712,16 @@ data_set_attribute_n(sentry_scope_data_t *data, const char *key, size_t key_len, static void data_remove_attribute(sentry_scope_data_t *data, const char *key) { - DATA_WRITE_LOCK(data) { sentry_value_remove_by_key(data->attributes, key); } + DATA_WRITE_LOCK (data) { + sentry_value_remove_by_key(data->attributes, key); + } } static void data_remove_attribute_n( sentry_scope_data_t *data, const char *key, size_t key_len) { - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { sentry_value_remove_by_key_n(data->attributes, key, key_len); } } @@ -740,7 +730,9 @@ static sentry_value_t data_ref_contexts(const sentry_scope_data_t *data) { sentry_value_t contexts = sentry_value_new_null(); - DATA_READ_LOCK(data) { contexts = sentry_value_incref(data->contexts); } + DATA_READ_LOCK (data) { + contexts = sentry_value_incref(data->contexts); + } return contexts; } @@ -749,8 +741,7 @@ data_set_context( sentry_scope_data_t *data, const char *key, sentry_value_t value) { bool did_set = false; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { did_set = sentry_value_set_by_key(data->contexts, key, value) == 0; } return did_set; @@ -761,8 +752,7 @@ data_set_context_n(sentry_scope_data_t *data, const char *key, size_t key_len, sentry_value_t value) { bool did_set = false; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { did_set = sentry_value_set_by_key_n(data->contexts, key, key_len, value) == 0; } @@ -780,8 +770,7 @@ static bool data_remove_context(sentry_scope_data_t *data, const char *key) { bool removed = false; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { removed = sentry_value_remove_by_key(data->contexts, key) == 0; } return removed; @@ -792,8 +781,7 @@ data_remove_context_n( sentry_scope_data_t *data, const char *key, size_t key_len) { char *removed_key = NULL; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { removed_key = sentry__value_remove_and_take_key_n(data->contexts, key, key_len); } @@ -804,8 +792,7 @@ static sentry_value_t data_ref_fingerprint(const sentry_scope_data_t *data) { sentry_value_t fingerprint = sentry_value_new_null(); - DATA_READ_LOCK(data) - { + DATA_READ_LOCK (data) { fingerprint = sentry_value_incref(data->fingerprint); } return fingerprint; @@ -814,8 +801,7 @@ data_ref_fingerprint(const sentry_scope_data_t *data) static void data_set_fingerprint(sentry_scope_data_t *data, sentry_value_t fingerprint) { - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { sentry__value_replace( &data->fingerprint, sentry_value_incref(fingerprint)); } @@ -825,14 +811,18 @@ static sentry_level_t data_get_level(const sentry_scope_data_t *data) { sentry_level_t level = SENTRY_LEVEL_ERROR; - DATA_READ_LOCK(data) { level = data->level; } + DATA_READ_LOCK (data) { + level = data->level; + } return level; } static void data_set_level(sentry_scope_data_t *data, sentry_level_t level) { - DATA_WRITE_LOCK(data) { data->level = level; } + DATA_WRITE_LOCK (data) { + data->level = level; + } } static sentry_uuid_t @@ -864,12 +854,16 @@ data_add_attachment(sentry_scope_data_t *data, sentry_value_t attachment, bool *did_add) { sentry_value_t added = sentry_value_new_null(); - DATA_WRITE_LOCK(data) - { + if (did_add) { + *did_add = false; + } + DATA_WRITE_LOCK (data) { size_t len = sentry_value_get_length(data->attachments); added = sentry__attachments_add(&data->attachments, attachment); - *did_add = !sentry_value_is_null(added) - && sentry_value_get_length(data->attachments) > len; + if (did_add) { + *did_add = !sentry_value_is_null(added) + && sentry_value_get_length(data->attachments) > len; + } } return added; } @@ -879,8 +873,7 @@ data_remove_attachment( sentry_scope_data_t *data, const sentry_uuid_t *attachment_id) { sentry_value_t removed = sentry_value_new_null(); - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { removed = sentry__attachments_remove(data->attachments, attachment_id); } return removed; @@ -890,8 +883,7 @@ static sentry_value_t data_take_attachments(sentry_scope_data_t *data) { sentry_value_t attachments = sentry_value_new_null(); - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { attachments = data->attachments; data->attachments = sentry__attachments_new(); } @@ -902,8 +894,7 @@ static sentry_transaction_t * data_ref_transaction_object(const sentry_scope_data_t *data) { sentry_transaction_t *transaction = NULL; - DATA_READ_LOCK(data) - { + DATA_READ_LOCK (data) { transaction = data->transaction_object; sentry__transaction_incref(transaction); } @@ -923,8 +914,7 @@ data_set_transaction_object( sentry_scope_data_t *data, sentry_transaction_t *transaction) { sentry__transaction_incref(transaction); - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { sentry__span_decref(data->span); data->span = NULL; sentry__transaction_decref(data->transaction_object); @@ -937,8 +927,7 @@ data_remove_transaction_object( sentry_scope_data_t *data, sentry_transaction_t *transaction) { bool removed = false; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { if (transaction && data->transaction_object == transaction) { data->transaction_object = NULL; removed = true; @@ -957,8 +946,7 @@ data_remove_transaction_value( const char *span_id = sentry_value_as_string( sentry_value_get_by_key(transaction, "span_id")); sentry_transaction_t *transaction_object = NULL; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { if (data->transaction_object && value_has_span_id(data->transaction_object->inner, span_id)) { transaction_object = data->transaction_object; @@ -974,8 +962,7 @@ data_restore_transaction_object( sentry_scope_data_t *data, sentry_transaction_t *transaction) { bool restored = false; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { if (!data->transaction_object && !data->span && transaction) { data->transaction_object = transaction; restored = true; @@ -988,8 +975,7 @@ static sentry_span_t * data_ref_span(const sentry_scope_data_t *data) { sentry_span_t *span = NULL; - DATA_READ_LOCK(data) - { + DATA_READ_LOCK (data) { span = data->span; sentry__span_incref(span); } @@ -1000,8 +986,7 @@ static void data_set_span(sentry_scope_data_t *data, sentry_span_t *span) { sentry__span_incref(span); - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { sentry__transaction_decref(data->transaction_object); data->transaction_object = NULL; sentry__span_decref(data->span); @@ -1013,8 +998,7 @@ static bool data_remove_span(sentry_scope_data_t *data, sentry_span_t *span) { bool removed = false; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { if (span && data->span == span) { data->span = NULL; removed = true; @@ -1032,8 +1016,7 @@ data_remove_span_value(sentry_scope_data_t *data, sentry_value_t span) const char *span_id = sentry_value_as_string(sentry_value_get_by_key(span, "span_id")); sentry_span_t *scope_span = NULL; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { if (data->span && value_has_span_id(data->span->inner, span_id)) { scope_span = data->span; data->span = NULL; @@ -1047,8 +1030,7 @@ static bool data_restore_span(sentry_scope_data_t *data, sentry_span_t *span) { bool restored = false; - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { if (!data->span && !data->transaction_object && span) { data->span = span; restored = true; @@ -1061,15 +1043,16 @@ static sentry_value_t data_ref_release(const sentry_scope_data_t *data) { sentry_value_t release = sentry_value_new_null(); - DATA_READ_LOCK(data) { release = sentry_value_incref(data->release); } + DATA_READ_LOCK (data) { + release = sentry_value_incref(data->release); + } return release; } static void data_set_release(sentry_scope_data_t *data, sentry_value_t value) { - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { sentry__value_replace(&data->release, sentry_value_incref(value)); sentry_value_set_by_key(data->dynamic_sampling_context, "release", sentry_value_incref(value)); @@ -1080,8 +1063,7 @@ static sentry_value_t data_ref_environment(const sentry_scope_data_t *data) { sentry_value_t environment = sentry_value_new_null(); - DATA_READ_LOCK(data) - { + DATA_READ_LOCK (data) { environment = sentry_value_incref(data->environment); } return environment; @@ -1090,8 +1072,7 @@ data_ref_environment(const sentry_scope_data_t *data) static void data_set_environment(sentry_scope_data_t *data, sentry_value_t value) { - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { sentry__value_replace(&data->environment, sentry_value_incref(value)); sentry_value_set_by_key(data->dynamic_sampling_context, "environment", sentry_value_incref(value)); @@ -1102,8 +1083,7 @@ static sentry_value_t data_ref_transaction(const sentry_scope_data_t *data) { sentry_value_t transaction = sentry_value_new_null(); - DATA_READ_LOCK(data) - { + DATA_READ_LOCK (data) { transaction = sentry_value_incref(data->transaction); } return transaction; @@ -1113,8 +1093,7 @@ static void data_set_transaction(sentry_scope_data_t *data, sentry_value_t value, const char *transaction, size_t transaction_len) { - DATA_WRITE_LOCK(data) - { + DATA_WRITE_LOCK (data) { sentry__value_replace(&data->transaction, sentry_value_incref(value)); if (data->transaction_object) { sentry_transaction_set_name_n( @@ -1130,8 +1109,7 @@ data_apply_to_telemetry(const sentry_scope_data_t *data, sentry_value_t os_name = sentry_value_new_null(); sentry_value_t os_version = sentry_value_new_null(); - DATA_READ_LOCK(data) - { + DATA_READ_LOCK (data) { sentry__value_merge_objects_shallow(attributes, data->attributes); sentry_value_t trace_id = sentry_value_get_by_key( From b7c5da8325a489399245220c4dcda7bdf7def7d9 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sun, 9 Aug 2026 21:26:08 +0200 Subject: [PATCH 20/22] fix(scope): Keep global scope alive during access Track active scope accesses and delay cleanup until they finish, while releasing the lifetime guard before backend scope flushing. --- src/sentry_scope.c | 26 ++++++++++ tests/unit/test_concurrency.c | 91 +++++++++++++++++++++++++++++++++++ tests/unit/tests.inc | 1 + 3 files changed, 118 insertions(+) diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 4441c87ede..bc314c793d 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -29,6 +29,9 @@ #endif static bool g_scope_initialized = false; +static size_t g_scope_accesses = 0; +static bool g_scope_idle_initialized = false; +static sentry_cond_t g_scope_idle; #ifdef SENTRY__MUTEX_INIT_DYN SENTRY__MUTEX_INIT_DYN(g_lock) #else @@ -1240,6 +1243,9 @@ sentry__scope_cleanup(void) { SENTRY__MUTEX_INIT_DYN_ONCE(g_lock); sentry__mutex_lock(&g_lock); + while (g_scope_accesses > 0) { + sentry__cond_wait(&g_scope_idle, &g_lock); + } if (g_scope_initialized) { g_scope_initialized = false; cleanup_global_data(g_scope.data); @@ -1263,11 +1269,29 @@ sentry__scope_begin(void) { SENTRY__MUTEX_INIT_DYN_ONCE(g_lock); sentry__mutex_lock(&g_lock); + if (!g_scope_idle_initialized) { + sentry__cond_init(&g_scope_idle); + g_scope_idle_initialized = true; + } sentry_scope_t *scope = get_scope(); + g_scope_accesses++; sentry__mutex_unlock(&g_lock); return scope; } +static void +end_scope_access(void) +{ + SENTRY__MUTEX_INIT_DYN_ONCE(g_lock); + sentry__mutex_lock(&g_lock); + assert(g_scope_accesses > 0); + g_scope_accesses--; + if (g_scope_accesses == 0) { + sentry__cond_wake(&g_scope_idle); + } + sentry__mutex_unlock(&g_lock); +} + static void finish_scope(bool flush, bool unlock) { @@ -1291,6 +1315,8 @@ finish_scope(bool flush, bool unlock) // we try to unlock the scope as soon as possible. The // backend will do its own `WITH_SCOPE` internally. sentry__mutex_unlock(&g_lock); + } else { + end_scope_access(); } if (flush) { diff --git a/tests/unit/test_concurrency.c b/tests/unit/test_concurrency.c index cdc3c739aa..3e1de13d24 100644 --- a/tests/unit/test_concurrency.c +++ b/tests/unit/test_concurrency.c @@ -3,6 +3,7 @@ #include "sentry_envelope.h" #include "sentry_options.h" #include "sentry_path.h" +#include "sentry_scope.h" #include "sentry_testsupport.h" #include "sentry_transport.h" @@ -241,3 +242,93 @@ SENTRY_TEST(concurrent_uninit) sentry_close(); } + +typedef struct { + sentry_mutex_t lock; + sentry_cond_t access_signal; + sentry_cond_t cleanup_signal; + bool access_started; + bool release_access; + bool cleanup_started; + bool cleanup_finished; +} scope_cleanup_state_t; + +SENTRY_THREAD_FN +scope_access_thread(void *data) +{ + scope_cleanup_state_t *state = data; + sentry_scope_t *scope = sentry__scope_begin(); + + sentry__mutex_lock(&state->lock); + state->access_started = true; + sentry__cond_wake(&state->access_signal); + while (!state->release_access) { + sentry__cond_wait(&state->access_signal, &state->lock); + } + sentry__mutex_unlock(&state->lock); + + (void)sentry__scope_get_level(scope); + sentry__scope_end(false); + return 0; +} + +SENTRY_THREAD_FN +scope_cleanup_thread(void *data) +{ + scope_cleanup_state_t *state = data; + + sentry__mutex_lock(&state->lock); + state->cleanup_started = true; + sentry__cond_wake(&state->cleanup_signal); + sentry__mutex_unlock(&state->lock); + + sentry__scope_cleanup(); + + sentry__mutex_lock(&state->lock); + state->cleanup_finished = true; + sentry__cond_wake(&state->cleanup_signal); + sentry__mutex_unlock(&state->lock); + return 0; +} + +SENTRY_TEST(scope_cleanup) +{ + scope_cleanup_state_t state = { 0 }; + sentry__mutex_init(&state.lock); + sentry__cond_init(&state.access_signal); + sentry__cond_init(&state.cleanup_signal); + + sentry_threadid_t access_thread; + sentry__thread_init(&access_thread); + TEST_ASSERT_INT_EQUAL( + sentry__thread_spawn(&access_thread, scope_access_thread, &state), 0); + + sentry__mutex_lock(&state.lock); + while (!state.access_started) { + sentry__cond_wait(&state.access_signal, &state.lock); + } + sentry__mutex_unlock(&state.lock); + + sentry_threadid_t cleanup_thread; + sentry__thread_init(&cleanup_thread); + TEST_ASSERT_INT_EQUAL( + sentry__thread_spawn(&cleanup_thread, scope_cleanup_thread, &state), 0); + + sentry__mutex_lock(&state.lock); + while (!state.cleanup_started) { + sentry__cond_wait(&state.cleanup_signal, &state.lock); + } + sentry__cond_wait_timeout(&state.cleanup_signal, &state.lock, 250); + TEST_CHECK(!state.cleanup_finished); + state.release_access = true; + sentry__cond_wake(&state.access_signal); + sentry__mutex_unlock(&state.lock); + + sentry__thread_join(access_thread); + sentry__thread_free(&access_thread); + sentry__thread_join(cleanup_thread); + sentry__thread_free(&cleanup_thread); + + TEST_CHECK(state.cleanup_finished); + sentry__mutex_free(&state.lock); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index f2cc1bba35..260ed10fe8 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -329,6 +329,7 @@ XX(scope_capture_metric_one_shot) XX(scope_capture_metric_user_owned) XX(scope_capture_unlocked) XX(scope_capture_user_owned) +XX(scope_cleanup) XX(scope_clear) XX(scope_clone) XX(scope_clone_independence) From dcf88de26214b573ff3e8e3e873bb71ec11ce8c2 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 12 Aug 2026 09:21:04 +0200 Subject: [PATCH 21/22] restore locks for attachments --- src/backends/sentry_backend_native.c | 5 ++- src/integrations/sentry_integration_wer.c | 33 ++++++++--------- src/sentry_core.c | 43 +++++++++++++---------- src/sentry_scope.c | 17 +++++---- src/sentry_scope.h | 2 +- tests/unit/test_attachments.c | 5 +-- tests/unit/test_scope.c | 9 ++--- 7 files changed, 64 insertions(+), 50 deletions(-) diff --git a/src/backends/sentry_backend_native.c b/src/backends/sentry_backend_native.c index 6cfe11a236..4a265e8dcf 100644 --- a/src/backends/sentry_backend_native.c +++ b/src/backends/sentry_backend_native.c @@ -738,12 +738,14 @@ native_backend_write_attachments(const sentry_path_t *event_path) return; } SENTRY_WITH_SCOPE (scope) { - sentry_value_t attachments = sentry__scope_get_attachments(scope); + sentry_value_t attachments = sentry__scope_ref_attachments(scope); if (sentry_value_get_length(attachments) == 0) { + sentry_value_decref(attachments); continue; } sentry_path_t *run_path = sentry__path_dir(event_path); if (!run_path) { + sentry_value_decref(attachments); continue; } sentry_path_t *attach_list_path @@ -790,6 +792,7 @@ native_backend_write_attachments(const sentry_path_t *event_path) sentry__path_free(attach_list_path); } sentry__path_free(run_path); + sentry_value_decref(attachments); } } diff --git a/src/integrations/sentry_integration_wer.c b/src/integrations/sentry_integration_wer.c index 87130a7be0..77b54dcf00 100644 --- a/src/integrations/sentry_integration_wer.c +++ b/src/integrations/sentry_integration_wer.c @@ -191,6 +191,18 @@ wer_remove_attachment(void *UNUSED(data), sentry_value_t attachment) } } +static void +wer_for_each_attachment( + sentry_scope_t *scope, void *data, void (*callback)(void *, sentry_value_t)) +{ + sentry_value_t attachments = sentry__scope_ref_attachments(scope); + size_t len = sentry_value_get_length(attachments); + for (size_t i = 0; i < len; i++) { + callback(data, sentry_value_get_by_index(attachments, i)); + } + sentry_value_decref(attachments); +} + static void wer_cleanup_tag(const char *key, sentry_value_t UNUSED(value), void *data) { @@ -211,12 +223,7 @@ wer_clear(void *data) sentry__value_foreach_key_value(tags, wer_cleanup_tag, wer_data); sentry_value_decref(tags); - sentry_value_t attachments = sentry__scope_get_attachments(scope); - size_t len = sentry_value_get_length(attachments); - for (size_t i = 0; i < len; i++) { - wer_remove_attachment( - wer_data, sentry_value_get_by_index(attachments, i)); - } + wer_for_each_attachment(scope, wer_data, wer_remove_attachment); } static void @@ -241,12 +248,7 @@ register_wer( if (sentry__scope_add_observer(scope, observer)) { wer_data->scope = scope; wer_data->observer = observer; - sentry_value_t attachments = sentry__scope_get_attachments(scope); - size_t len = sentry_value_get_length(attachments); - for (size_t i = 0; i < len; i++) { - wer_add_attachment( - wer_data, sentry_value_get_by_index(attachments, i)); - } + wer_for_each_attachment(scope, wer_data, wer_add_attachment); } } @@ -264,12 +266,7 @@ unregister_wer( sentry__value_foreach_key_value(tags, wer_cleanup_tag, wer_data); sentry_value_decref(tags); - sentry_value_t attachments = sentry__scope_get_attachments(scope); - size_t len = sentry_value_get_length(attachments); - for (size_t i = 0; i < len; i++) { - wer_remove_attachment( - wer_data, sentry_value_get_by_index(attachments, i)); - } + wer_for_each_attachment(scope, wer_data, wer_remove_attachment); sentry__scope_remove_observer(scope, wer_data->observer); wer_data->scope = NULL; diff --git a/src/sentry_core.c b/src/sentry_core.c index fb43fa8767..c5961db518 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -706,17 +706,20 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, } SENTRY_WITH_SCOPE (scope) { - sentry_value_t attachments = sentry__scope_get_attachments(scope); - if (local_scope - && sentry_value_get_length( - sentry__scope_get_attachments(local_scope)) - > 0) { - // all attachments merged from multiple scopes - sentry__attachments_extend(&all_attachments, - sentry__scope_get_attachments(local_scope)); - sentry__attachments_extend( - &all_attachments, sentry__scope_get_attachments(scope)); - attachments = all_attachments; + sentry_value_t global_attachments + = sentry__scope_ref_attachments(scope); + sentry_value_t attachments = global_attachments; + if (local_scope) { + sentry_value_t local_attachments + = sentry__scope_ref_attachments(local_scope); + if (sentry_value_get_length(local_attachments) > 0) { + // all attachments merged from multiple scopes + sentry__attachments_extend(&all_attachments, local_attachments); + sentry__attachments_extend( + &all_attachments, global_attachments); + attachments = all_attachments; + } + sentry_value_decref(local_attachments); } // otherwise only global scope has attachments sentry__envelope_add_attachments(envelope, attachments, options); @@ -724,6 +727,7 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, sentry__cache_attachment_refs(envelope, attachments, options, options->run->cache_path, options->run->run_path); } + sentry_value_decref(global_attachments); } sentry_value_decref(all_attachments); @@ -848,15 +852,18 @@ prepare_user_feedback(const sentry_options_t *options, sentry__attachments_extend(&all_attachments, hint->attachments); } if (local_scope) { - sentry__attachments_extend( - &all_attachments, sentry__scope_get_attachments(local_scope)); + sentry_value_t local_attachments + = sentry__scope_ref_attachments(local_scope); + sentry__attachments_extend(&all_attachments, local_attachments); + sentry_value_decref(local_attachments); } SENTRY_WITH_SCOPE (scope) { - sentry_value_t attachments = sentry__scope_get_attachments(scope); + sentry_value_t global_attachments + = sentry__scope_ref_attachments(scope); + sentry_value_t attachments = global_attachments; if (sentry_value_get_length(all_attachments) > 0) { - sentry__attachments_extend( - &all_attachments, sentry__scope_get_attachments(scope)); + sentry__attachments_extend(&all_attachments, global_attachments); attachments = all_attachments; } sentry__envelope_add_attachments(envelope, attachments, options); @@ -864,6 +871,7 @@ prepare_user_feedback(const sentry_options_t *options, sentry__cache_attachment_refs(envelope, attachments, options, options->run->cache_path, options->run->run_path); } + sentry_value_decref(global_attachments); } sentry_value_decref(all_attachments); @@ -2051,8 +2059,7 @@ sentry_clear_attachments(void) { SENTRY_WITH_OPTIONS (options) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_t attachments - = sentry__scope_take_attachments(scope); + sentry_value_t attachments = sentry__scope_take_attachments(scope); size_t len = sentry_value_get_length(attachments); for (size_t i = 0; i < len; i++) { sentry_value_t attachment diff --git a/src/sentry_scope.c b/src/sentry_scope.c index bc314c793d..ea3c10f1d5 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -331,6 +331,7 @@ clone_data(const sentry_scope_data_t *source) clone->level = source->level; clone->last_event_id = source->last_event_id; clone->client_sdk = sentry__value_clone(source->client_sdk); + clone->attachments = sentry__attachments_new(); sentry__attachments_extend(&clone->attachments, source->attachments); clone->transaction_object = source->transaction_object; sentry__transaction_incref(clone->transaction_object); @@ -847,14 +848,18 @@ data_set_last_event_id(sentry_scope_data_t *data, sentry_uuid_t event_id) } static sentry_value_t -data_get_attachments(const sentry_scope_data_t *data) +data_ref_attachments(const sentry_scope_data_t *data) { - return data->attachments; + sentry_value_t attachments = sentry_value_new_null(); + DATA_READ_LOCK (data) { + attachments = sentry_value_incref(data->attachments); + } + return attachments; } static sentry_value_t -data_add_attachment(sentry_scope_data_t *data, sentry_value_t attachment, - bool *did_add) +data_add_attachment( + sentry_scope_data_t *data, sentry_value_t attachment, bool *did_add) { sentry_value_t added = sentry_value_new_null(); if (did_add) { @@ -2322,9 +2327,9 @@ sentry_scope_set_span(sentry_scope_t *scope, sentry_span_t *span) } sentry_value_t -sentry__scope_get_attachments(const sentry_scope_t *scope) +sentry__scope_ref_attachments(const sentry_scope_t *scope) { - return data_get_attachments(scope->data); + return data_ref_attachments(scope->data); } sentry_value_t diff --git a/src/sentry_scope.h b/src/sentry_scope.h index d2caee46be..4b91b61f0d 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -160,7 +160,7 @@ sentry_value_t sentry__scope_ref_user(const sentry_scope_t *scope); sentry_level_t sentry__scope_get_level(const sentry_scope_t *scope); sentry_value_t sentry__scope_ref_client_sdk(const sentry_scope_t *scope); -sentry_value_t sentry__scope_get_attachments(const sentry_scope_t *scope); +sentry_value_t sentry__scope_ref_attachments(const sentry_scope_t *scope); sentry_value_t sentry__scope_add_attachment( sentry_scope_t *scope, sentry_value_t attachment); sentry_value_t sentry__scope_take_attachments(sentry_scope_t *scope); diff --git a/tests/unit/test_attachments.c b/tests/unit/test_attachments.c index 73d50bcbee..db223b1c5a 100644 --- a/tests/unit/test_attachments.c +++ b/tests/unit/test_attachments.c @@ -17,8 +17,9 @@ static void add_scope_attachments(sentry_envelope_t *envelope) { SENTRY_WITH_SCOPE (scope) { - sentry__envelope_add_attachments( - envelope, sentry__scope_get_attachments(scope), NULL); + sentry_value_t attachments = sentry__scope_ref_attachments(scope); + sentry__envelope_add_attachments(envelope, attachments, NULL); + sentry_value_decref(attachments); } } diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index ea756e8f06..2e92ab9d38 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -2607,8 +2607,7 @@ SENTRY_TEST(scope_clone_preserves_data) TEST_CHECK(!sentry_uuid_is_nil(&attachment_id)); sentry_scope_t *clone = sentry_scope_clone(scope); - sentry_value_decref( - sentry__scope_remove_attachment(scope, &attachment_id)); + sentry_value_decref(sentry__scope_remove_attachment(scope, &attachment_id)); sentry_value_t clone_tag = scope_value_get_by_key(sentry__scope_ref_tags, clone, "tag_key"); @@ -2640,8 +2639,8 @@ SENTRY_TEST(scope_clone_preserves_data) TEST_CHECK_INT_EQUAL(scope_breadcrumb_count(clone), 1); // Attachments are deep-copied into an independent list. - sentry_value_t clone_attachments = sentry__scope_get_attachments(clone); - sentry_value_t scope_attachments = sentry__scope_get_attachments(scope); + sentry_value_t clone_attachments = sentry__scope_ref_attachments(clone); + sentry_value_t scope_attachments = sentry__scope_ref_attachments(scope); TEST_CHECK_INT_EQUAL(sentry_value_get_length(clone_attachments), 1); TEST_CHECK_INT_EQUAL(sentry_value_get_length(scope_attachments), 0); TEST_CHECK(clone_attachments._bits != scope_attachments._bits); @@ -2650,6 +2649,8 @@ SENTRY_TEST(scope_clone_preserves_data) TEST_CHECK_STRING_EQUAL( sentry__attachment_get_filename(clone_attachment), "file.bin"); TEST_CHECK_INT_EQUAL(sentry__attachment_get_size(clone_attachment), 7); + sentry_value_decref(scope_attachments); + sentry_value_decref(clone_attachments); sentry_scope_free(clone); sentry_scope_free(scope); From b899738f5905a935caa13cf637a30601b07bd920 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 12 Aug 2026 18:02:04 +0200 Subject: [PATCH 22/22] clone (cow) attachments guard against concurrent modifications --- src/backends/sentry_backend_native.c | 2 +- src/integrations/sentry_integration_wer.c | 2 +- src/sentry_core.c | 12 +++-- src/sentry_scope.c | 8 +-- src/sentry_scope.h | 9 +++- tests/unit/test_attachments.c | 6 ++- tests/unit/test_scope.c | 66 ++++++++++++++++++++++- tests/unit/tests.inc | 1 + 8 files changed, 90 insertions(+), 16 deletions(-) diff --git a/src/backends/sentry_backend_native.c b/src/backends/sentry_backend_native.c index 4a265e8dcf..409720b92e 100644 --- a/src/backends/sentry_backend_native.c +++ b/src/backends/sentry_backend_native.c @@ -738,7 +738,7 @@ native_backend_write_attachments(const sentry_path_t *event_path) return; } SENTRY_WITH_SCOPE (scope) { - sentry_value_t attachments = sentry__scope_ref_attachments(scope); + sentry_value_t attachments = sentry__scope_clone_attachments(scope); if (sentry_value_get_length(attachments) == 0) { sentry_value_decref(attachments); continue; diff --git a/src/integrations/sentry_integration_wer.c b/src/integrations/sentry_integration_wer.c index 77b54dcf00..73ac8ee92c 100644 --- a/src/integrations/sentry_integration_wer.c +++ b/src/integrations/sentry_integration_wer.c @@ -195,7 +195,7 @@ static void wer_for_each_attachment( sentry_scope_t *scope, void *data, void (*callback)(void *, sentry_value_t)) { - sentry_value_t attachments = sentry__scope_ref_attachments(scope); + sentry_value_t attachments = sentry__scope_clone_attachments(scope); size_t len = sentry_value_get_length(attachments); for (size_t i = 0; i < len; i++) { callback(data, sentry_value_get_by_index(attachments, i)); diff --git a/src/sentry_core.c b/src/sentry_core.c index c5961db518..41a40e9c76 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -707,11 +707,11 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, SENTRY_WITH_SCOPE (scope) { sentry_value_t global_attachments - = sentry__scope_ref_attachments(scope); + = sentry__scope_clone_attachments(scope); sentry_value_t attachments = global_attachments; if (local_scope) { sentry_value_t local_attachments - = sentry__scope_ref_attachments(local_scope); + = sentry__scope_clone_attachments(local_scope); if (sentry_value_get_length(local_attachments) > 0) { // all attachments merged from multiple scopes sentry__attachments_extend(&all_attachments, local_attachments); @@ -853,14 +853,14 @@ prepare_user_feedback(const sentry_options_t *options, } if (local_scope) { sentry_value_t local_attachments - = sentry__scope_ref_attachments(local_scope); + = sentry__scope_clone_attachments(local_scope); sentry__attachments_extend(&all_attachments, local_attachments); sentry_value_decref(local_attachments); } SENTRY_WITH_SCOPE (scope) { sentry_value_t global_attachments - = sentry__scope_ref_attachments(scope); + = sentry__scope_clone_attachments(scope); sentry_value_t attachments = global_attachments; if (sentry_value_get_length(all_attachments) > 0) { sentry__attachments_extend(&all_attachments, global_attachments); @@ -2010,7 +2010,9 @@ sentry_add_attachment(sentry_value_t attachment) sentry_value_t added = sentry_value_new_null(); SENTRY_WITH_SCOPE_MUT (scope) { - added = sentry__attachments_find(scope->attachments, attachment); + sentry_value_t attachments = sentry__scope_clone_attachments(scope); + added = sentry__attachments_find(attachments, attachment); + sentry_value_decref(attachments); if (sentry_value_is_null(added)) { if (options->backend && options->backend->add_attachment_func) { options->backend->add_attachment_func( diff --git a/src/sentry_scope.c b/src/sentry_scope.c index ea3c10f1d5..bfe798edac 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -848,11 +848,11 @@ data_set_last_event_id(sentry_scope_data_t *data, sentry_uuid_t event_id) } static sentry_value_t -data_ref_attachments(const sentry_scope_data_t *data) +data_clone_attachments(const sentry_scope_data_t *data) { sentry_value_t attachments = sentry_value_new_null(); DATA_READ_LOCK (data) { - attachments = sentry_value_incref(data->attachments); + attachments = sentry__value_clone(data->attachments); } return attachments; } @@ -2327,9 +2327,9 @@ sentry_scope_set_span(sentry_scope_t *scope, sentry_span_t *span) } sentry_value_t -sentry__scope_ref_attachments(const sentry_scope_t *scope) +sentry__scope_clone_attachments(const sentry_scope_t *scope) { - return data_ref_attachments(scope->data); + return data_clone_attachments(scope->data); } sentry_value_t diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 4b91b61f0d..543a6899d2 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -160,7 +160,14 @@ sentry_value_t sentry__scope_ref_user(const sentry_scope_t *scope); sentry_level_t sentry__scope_get_level(const sentry_scope_t *scope); sentry_value_t sentry__scope_ref_client_sdk(const sentry_scope_t *scope); -sentry_value_t sentry__scope_ref_attachments(const sentry_scope_t *scope); +/** + * Returns an owned copy-on-write snapshot of the scope's attachment list. + * + * The list remains stable after the scope data read lock is released. Published + * attachment elements remain shared because they are frozen. The caller must + * release the snapshot with `sentry_value_decref`. + */ +sentry_value_t sentry__scope_clone_attachments(const sentry_scope_t *scope); sentry_value_t sentry__scope_add_attachment( sentry_scope_t *scope, sentry_value_t attachment); sentry_value_t sentry__scope_take_attachments(sentry_scope_t *scope); diff --git a/tests/unit/test_attachments.c b/tests/unit/test_attachments.c index db223b1c5a..9c3fd86ce6 100644 --- a/tests/unit/test_attachments.c +++ b/tests/unit/test_attachments.c @@ -17,7 +17,7 @@ static void add_scope_attachments(sentry_envelope_t *envelope) { SENTRY_WITH_SCOPE (scope) { - sentry_value_t attachments = sentry__scope_ref_attachments(scope); + sentry_value_t attachments = sentry__scope_clone_attachments(scope); sentry__envelope_add_attachments(envelope, attachments, NULL); sentry_value_decref(attachments); } @@ -390,7 +390,9 @@ SENTRY_TEST(attachment_properties) sentry_init(options); SENTRY_WITH_SCOPE (scope) { - TEST_CHECK_INT_EQUAL(sentry_value_get_length(scope->attachments), 0); + sentry_value_t attachments = sentry__scope_clone_attachments(scope); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(attachments), 0); + sentry_value_decref(attachments); } TEST_CHECK(sentry_value_is_null(sentry_attachment_from_file(NULL))); diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 2e92ab9d38..b7269611ce 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -2639,8 +2639,8 @@ SENTRY_TEST(scope_clone_preserves_data) TEST_CHECK_INT_EQUAL(scope_breadcrumb_count(clone), 1); // Attachments are deep-copied into an independent list. - sentry_value_t clone_attachments = sentry__scope_ref_attachments(clone); - sentry_value_t scope_attachments = sentry__scope_ref_attachments(scope); + sentry_value_t clone_attachments = sentry__scope_clone_attachments(clone); + sentry_value_t scope_attachments = sentry__scope_clone_attachments(scope); TEST_CHECK_INT_EQUAL(sentry_value_get_length(clone_attachments), 1); TEST_CHECK_INT_EQUAL(sentry_value_get_length(scope_attachments), 0); TEST_CHECK(clone_attachments._bits != scope_attachments._bits); @@ -2656,6 +2656,68 @@ SENTRY_TEST(scope_clone_preserves_data) sentry_scope_free(scope); } +SENTRY_TEST(scope_attachments) +{ + sentry_scope_t *scope = sentry_scope_new(); + sentry_uuid_t first_id = sentry_scope_add_attachment( + scope, sentry_attachment_from_bytes("first", 5, "first.txt")); + sentry_uuid_t second_id = sentry_scope_add_attachment( + scope, sentry_attachment_from_bytes("second", 6, "second.txt")); + TEST_CHECK(!sentry_uuid_is_nil(&first_id)); + TEST_CHECK(!sentry_uuid_is_nil(&second_id)); + + sentry_value_t snapshot = sentry__scope_clone_attachments(scope); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(snapshot), 2); + TEST_CHECK_STRING_EQUAL( + sentry__attachment_get_filename(sentry_value_get_by_index(snapshot, 0)), + "first.txt"); + TEST_CHECK_STRING_EQUAL( + sentry__attachment_get_filename(sentry_value_get_by_index(snapshot, 1)), + "second.txt"); + + sentry_uuid_t third_id = sentry_scope_add_attachment( + scope, sentry_attachment_from_bytes("third", 5, "third.txt")); + TEST_CHECK(!sentry_uuid_is_nil(&third_id)); + + sentry_value_t current = sentry__scope_clone_attachments(scope); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(current), 3); + TEST_CHECK_STRING_EQUAL( + sentry__attachment_get_filename(sentry_value_get_by_index(current, 2)), + "third.txt"); + sentry_value_decref(current); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(snapshot), 2); + + sentry_value_t removed = sentry__scope_remove_attachment(scope, &first_id); + TEST_CHECK(!sentry_value_is_null(removed)); + sentry_value_decref(removed); + + current = sentry__scope_clone_attachments(scope); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(current), 2); + TEST_CHECK_STRING_EQUAL( + sentry__attachment_get_filename(sentry_value_get_by_index(current, 0)), + "second.txt"); + TEST_CHECK_STRING_EQUAL( + sentry__attachment_get_filename(sentry_value_get_by_index(current, 1)), + "third.txt"); + sentry_value_decref(current); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(snapshot), 2); + TEST_CHECK_STRING_EQUAL( + sentry__attachment_get_filename(sentry_value_get_by_index(snapshot, 0)), + "first.txt"); + + sentry_scope_clear(scope); + current = sentry__scope_clone_attachments(scope); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(current), 0); + sentry_value_decref(current); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(snapshot), 2); + TEST_CHECK_STRING_EQUAL( + sentry__attachment_get_filename(sentry_value_get_by_index(snapshot, 1)), + "second.txt"); + + sentry_value_decref(snapshot); + sentry_scope_free(scope); +} + SENTRY_TEST(scope_clone_shares_span) { SENTRY_TEST_OPTIONS_NEW(options); diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 260ed10fe8..4dab95907e 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -316,6 +316,7 @@ XX(rwlock_write_blocks_writer) XX(sampling_before_send) XX(sampling_decision) XX(sampling_transaction) +XX(scope_attachments) XX(scope_bind_span) XX(scope_bind_span_or_transaction_not_both) XX(scope_bind_transaction_object)