diff --git a/.clang-format b/.clang-format index 84446160fd..b6bcfbfd22 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', 'SENTRY_SCOPE_READ_LOCK', 'SENTRY_SCOPE_WRITE_LOCK'] InsertNewlineAtEOF: True diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index b62af46e86..6475391bd7 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -516,7 +516,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(scope->breadcrumbs)); + sentry__scope_breadcrumbs_to_list(scope)); } sentry__session_replay_flush_pending( @@ -1168,10 +1168,9 @@ make_attachment_path(const sentry_path_t *run_path, sentry_value_t attachment) } static void -crashpad_backend_add_attachment(sentry_backend_t *backend, - sentry_value_t attachment, const sentry_options_t *UNUSED(options)) +crashpad_backend_add_attachment(void *state, sentry_value_t attachment) { - auto *data = static_cast(backend->data); + auto *data = static_cast(state); if (!data || !data->client) { return; } @@ -1200,10 +1199,9 @@ crashpad_backend_add_attachment(sentry_backend_t *backend, } static void -crashpad_backend_remove_attachment( - sentry_backend_t *backend, sentry_value_t attachment) +crashpad_backend_remove_attachment(void *state, sentry_value_t attachment) { - auto *data = static_cast(backend->data); + auto *data = static_cast(state); if (!data || !data->client) { return; } @@ -1259,8 +1257,14 @@ sentry__backend_new(void) backend->prune_database_func = crashpad_backend_prune_database; #if defined(SENTRY_PLATFORM_WINDOWS) || defined(SENTRY_PLATFORM_LINUX) \ || defined(SENTRY_PLATFORM_MACOS) - backend->add_attachment_func = crashpad_backend_add_attachment; - backend->remove_attachment_func = crashpad_backend_remove_attachment; + backend->scope_observer = sentry__scope_observer_new(); + if (backend->scope_observer) { + backend->scope_observer->data = data; + backend->scope_observer->add_attachment + = crashpad_backend_add_attachment; + backend->scope_observer->remove_attachment + = crashpad_backend_remove_attachment; + } #endif backend->data = data; backend->can_capture_after_shutdown = true; diff --git a/src/backends/sentry_backend_native.c b/src/backends/sentry_backend_native.c index 286d0873b2..1eb0ffcfa1 100644 --- a/src/backends/sentry_backend_native.c +++ b/src/backends/sentry_backend_native.c @@ -174,6 +174,7 @@ wer_register_module(uint64_t app_tid) typedef struct { sentry_crash_ipc_t *ipc; pid_t daemon_pid; + sentry_path_t *run_path; sentry_path_t *event_path; sentry_path_t *breadcrumb1_path; sentry_path_t *breadcrumb2_path; @@ -244,6 +245,10 @@ native_backend_startup( return 1; } backend->data = state; + state->run_path = sentry__path_clone(options->run->run_path); + if (backend->scope_observer) { + backend->scope_observer->data = state; + } // Initialize IPC (protected by global synchronization for concurrent // access) @@ -715,6 +720,7 @@ native_backend_free(sentry_backend_t *backend) sentry__path_free(state->breadcrumb1_path); sentry__path_free(state->breadcrumb2_path); sentry__path_free(state->envelope_path); + sentry__path_free(state->run_path); sentry_free(state); } @@ -753,12 +759,14 @@ 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_load_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 @@ -807,6 +815,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); } } @@ -931,18 +940,19 @@ native_backend_add_breadcrumb(sentry_backend_t *backend, } static void -native_backend_add_attachment(sentry_backend_t *backend, - sentry_value_t attachment, const sentry_options_t *options) +native_backend_add_attachment(void *data, sentry_value_t attachment) { - (void)backend; // Unused + native_backend_state_t *state = (native_backend_state_t *)data; + if (!state) { + return; + } // For buffer attachments, derive a path in the run directory and write to // disk size_t bytes_len = 0; const char *bytes = sentry__attachment_get_bytes(attachment, &bytes_len); if (bytes) { - sentry_path_t *path - = make_attachment_path(options->run->run_path, attachment); + sentry_path_t *path = make_attachment_path(state->run_path, attachment); if (!path) { const char *filename = sentry__attachment_get_filename(attachment); SENTRY_WARNF("failed to create path for native backend attachment " @@ -1137,7 +1147,10 @@ sentry__backend_new(void) backend->except_func = native_backend_except; backend->flush_scope_func = native_backend_flush_scope; backend->add_breadcrumb_func = native_backend_add_breadcrumb; - backend->add_attachment_func = native_backend_add_attachment; + backend->scope_observer = sentry__scope_observer_new(); + if (backend->scope_observer) { + backend->scope_observer->add_attachment = native_backend_add_attachment; + } backend->user_consent_changed_func = native_backend_user_consent_changed; backend->can_capture_after_shutdown = false; diff --git a/src/integrations/sentry_integration_wer.c b/src/integrations/sentry_integration_wer.c index bf1c5eaf93..9cfb950bbe 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_load_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) { @@ -207,13 +219,11 @@ wer_clear(void *data) return; } - sentry__value_foreach_key_value(scope->tags, wer_cleanup_tag, wer_data); + sentry_value_t tags = sentry__scope_load_tags(scope); + sentry__value_foreach_key_value(tags, wer_cleanup_tag, wer_data); + sentry_value_decref(tags); - size_t len = sentry_value_get_length(scope->attachments); - for (size_t i = 0; i < len; i++) { - wer_remove_attachment( - wer_data, sentry_value_get_by_index(scope->attachments, i)); - } + wer_for_each_attachment(scope, wer_data, wer_remove_attachment); } static void @@ -238,11 +248,7 @@ 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); - for (size_t i = 0; i < len; i++) { - wer_add_attachment( - wer_data, sentry_value_get_by_index(scope->attachments, i)); - } + wer_for_each_attachment(scope, wer_data, wer_add_attachment); } } @@ -256,13 +262,11 @@ unregister_wer( return; } - sentry__value_foreach_key_value(scope->tags, wer_cleanup_tag, wer_data); + sentry_value_t tags = sentry__scope_load_tags(scope); + sentry__value_foreach_key_value(tags, wer_cleanup_tag, wer_data); + sentry_value_decref(tags); - size_t len = sentry_value_get_length(scope->attachments); - for (size_t i = 0; i < len; i++) { - wer_remove_attachment( - wer_data, sentry_value_get_by_index(scope->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_backend.c b/src/sentry_backend.c index f16d9ec12a..321da7a5ff 100644 --- a/src/sentry_backend.c +++ b/src/sentry_backend.c @@ -9,5 +9,6 @@ sentry__backend_free(sentry_backend_t *backend) if (backend->free_func) { backend->free_func(backend); } + sentry_free(backend->scope_observer); sentry_free(backend); } diff --git a/src/sentry_backend.h b/src/sentry_backend.h index c1f46a57f3..dffcc98de9 100644 --- a/src/sentry_backend.h +++ b/src/sentry_backend.h @@ -25,9 +25,7 @@ struct sentry_backend_s { void (*user_consent_changed_func)(sentry_backend_t *); uint64_t (*get_last_crash_func)(sentry_backend_t *); void (*prune_database_func)(sentry_backend_t *); - void (*add_attachment_func)( - sentry_backend_t *, sentry_value_t, const sentry_options_t *options); - void (*remove_attachment_func)(sentry_backend_t *, sentry_value_t); + sentry_scope_observer_t *scope_observer; void *data; // Whether this backend still runs after shutdown_func was called. bool can_capture_after_shutdown; diff --git a/src/sentry_core.c b/src/sentry_core.c index bf2bb2d017..071d4a7ae3 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,11 @@ 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__scope_apply_options(scope, options); + if (backend && backend->scope_observer + && !sentry__scope_add_observer(scope, backend->scope_observer)) { + backend->scope_observer = NULL; } - 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); - register_integrations(scope, options); } if (backend && backend->user_consent_changed_func) { @@ -339,8 +295,13 @@ sentry_close(void) // flushed. This prevents a potential deadlock on the options during // envelope creation. SENTRY_WITH_OPTIONS (options) { - if (options->num_integrations) { + if (options->backend || options->num_integrations) { SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) { + if (options->backend && options->backend->scope_observer) { + sentry__scope_remove_observer( + scope, options->backend->scope_observer); + options->backend->scope_observer = NULL; + } unregister_integrations(scope, options); } } @@ -521,7 +482,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); } } @@ -756,14 +717,20 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, } SENTRY_WITH_SCOPE (scope) { - sentry_value_t attachments = scope->attachments; - if (local_scope - && sentry_value_get_length(local_scope->attachments) > 0) { - // all attachments merged from multiple scopes - sentry__attachments_extend( - &all_attachments, local_scope->attachments); - sentry__attachments_extend(&all_attachments, scope->attachments); - attachments = all_attachments; + sentry_value_t global_attachments + = sentry__scope_load_attachments(scope); + sentry_value_t attachments = global_attachments; + if (local_scope) { + sentry_value_t local_attachments + = sentry__scope_load_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); @@ -771,6 +738,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); @@ -895,13 +863,18 @@ 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_value_t local_attachments + = sentry__scope_load_attachments(local_scope); + sentry__attachments_extend(&all_attachments, local_attachments); + sentry_value_decref(local_attachments); } SENTRY_WITH_SCOPE (scope) { - sentry_value_t attachments = scope->attachments; + sentry_value_t global_attachments + = sentry__scope_load_attachments(scope); + sentry_value_t attachments = global_attachments; if (sentry_value_get_length(all_attachments) > 0) { - sentry__attachments_extend(&all_attachments, scope->attachments); + sentry__attachments_extend(&all_attachments, global_attachments); attachments = all_attachments; } sentry__envelope_add_attachments(envelope, attachments, options); @@ -909,6 +882,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); @@ -988,11 +962,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); } } @@ -1006,12 +976,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); } } @@ -1091,9 +1056,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); } } @@ -1101,12 +1064,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); } } @@ -1130,9 +1088,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); } } @@ -1140,12 +1096,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); } } @@ -1218,7 +1169,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); } } @@ -1246,9 +1197,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); } } @@ -1256,12 +1205,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); } } @@ -1327,7 +1271,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)) { @@ -1348,8 +1292,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); } } @@ -1365,15 +1309,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); } } @@ -1436,12 +1372,12 @@ 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_value_t scope_trace_id = sentry_value_get_by_key( - sentry_value_get_by_key( - scope->propagation_context, "trace"), - "trace_id"); - sentry_value_incref(scope_trace_id); + sentry__scope_regenerate_propagation_context(scope); + sentry_value_t trace_context + = sentry__scope_load_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"); @@ -1454,9 +1390,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_value_get_by_key(scope->propagation_context, "trace"), - "sample_rand")); + sentry_value_t trace_context = sentry__scope_load_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 }; @@ -1502,10 +1439,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); @@ -1523,28 +1457,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"), - "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 @@ -1604,7 +1525,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); } } @@ -1612,7 +1533,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); } } @@ -1764,18 +1685,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 @@ -1834,10 +1744,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); @@ -2114,16 +2021,7 @@ 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); - if (sentry_value_is_null(added)) { - if (options->backend && options->backend->add_attachment_func) { - options->backend->add_attachment_func( - options->backend, attachment, options); - } - added = sentry__scope_add_attachment(scope, attachment); - } else { - sentry_value_decref(attachment); - } + added = sentry__scope_add_attachment(scope, attachment); } sentry_options_free((sentry_options_t *)options); sentry_uuid_t uuid = sentry__attachment_get_id(added); @@ -2163,17 +2061,11 @@ 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 = sentry_value_get_by_index(attachments, i); - if (options->backend - && options->backend->remove_attachment_func) { - options->backend->remove_attachment_func( - options->backend, attachment); - } SENTRY_SCOPE_NOTIFY(scope, remove_attachment, attachment); } sentry_value_decref(attachments); @@ -2190,14 +2082,9 @@ 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) { - options->backend->remove_attachment_func( - options->backend, removed); - } SENTRY_SCOPE_NOTIFY(scope, remove_attachment, removed); } sentry_value_decref(removed); diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index f94cba4639..b30958d144 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -415,10 +415,11 @@ 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); - sample_rand = sentry_value_as_double(sentry_value_get_by_key( - sentry_value_get_by_key(scope->propagation_context, "trace"), - "sample_rand")); + dsc = sentry__scope_load_dsc(scope); + sentry_value_t trace_context = sentry__scope_load_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( @@ -496,7 +497,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__scope_load_dsc(scope); } if (!sentry_value_is_null(dsc)) { diff --git a/src/sentry_scope.c b/src/sentry_scope.c index fe83f05955..819c3c3cf0 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 @@ -27,14 +28,75 @@ # define SENTRY_BACKEND "native" #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 bool g_scope_initialized = false; static sentry_scope_t g_scope = { 0 }; +static sentry_scope_data_t g_scope_data = { 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 static sentry_mutex_t g_lock = SENTRY__MUTEX_INIT; #endif +#define SENTRY_SCOPE_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 \ + = (sentry__rwlock_read_lock( \ + (sentry_rwlock_t *)&_locked_data->rwlock), \ + true); \ + _locked_once; _locked_once = false) + +#define SENTRY_SCOPE_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 \ + = (sentry__rwlock_write_lock(&_locked_data->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) { @@ -70,32 +132,225 @@ get_client_sdk(void) } static void -init_scope(sentry_scope_t *scope) -{ - scope->release = NULL; - scope->environment = NULL; - scope->transaction = 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_scope_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_scope_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_scope_data(void) +{ + sentry_scope_data_t *data = SENTRY_MAKE(sentry_scope_data_t); + if (data) { + sentry__rwlock_init(&data->rwlock); + init_scope_data(data); + } + return data; +} + +static void +free_scope_data(sentry_scope_data_t *data) +{ + if (!data) { + return; + } + cleanup_scope_data(data); + sentry__rwlock_free(&data->rwlock); + sentry_free(data); +} + +static void +cleanup_global_data(sentry_scope_data_t *data) +{ + cleanup_scope_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 +clear_scope_data(sentry_scope_data_t *data) +{ + SENTRY_SCOPE_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_scope_data(data); + init_scope_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_scope_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); + SENTRY_SCOPE_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); + clone->attachments = sentry__attachments_new(); + 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 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")); +} + +void +sentry__scope_update_dsc(sentry_scope_t *scope, const sentry_options_t *options) +{ + sentry_scope_data_t *data = scope->data; + 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_SCOPE_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 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 bool +init_scope(sentry_scope_t *scope, sentry_scope_data_t *data) +{ + scope->refcount = 1; + scope->data = data ? data : new_scope_data(); + if (!scope->data) { + return false; + } scope->observers = NULL; scope->num_observers = 0; scope->is_notifying = 0; scope->pending_flush = false; + sentry__mutex_init(&scope->observers_lock); scope->one_shot = false; + return true; } static sentry_scope_t * @@ -106,10 +361,13 @@ get_scope(void) } memset(&g_scope, 0, sizeof(sentry_scope_t)); - 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_scope_data(&g_scope_data); + if (!init_scope(&g_scope, &g_scope_data)) { + return &g_scope; + } + init_global_data(g_scope.data); g_scope_initialized = true; @@ -117,71 +375,119 @@ get_scope(void) } static void -cleanup_scope(sentry_scope_t *scope) +cleanup_observers(sentry_scope_t *scope) { - sentry_free(scope->release); - sentry_free(scope->environment); - sentry_free(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]); } sentry_free(scope->observers); scope->observers = NULL; scope->num_observers = 0; + scope->is_notifying = 0; scope->pending_flush = false; } +static void +cleanup_scope(sentry_scope_t *scope) +{ + free_scope_data(scope->data); + scope->data = NULL; + cleanup_observers(scope); + sentry__mutex_free(&scope->observers_lock); +} + +sentry_scope_t * +sentry__scope_incref(sentry_scope_t *scope) +{ + if (scope) { + sentry__atomic_fetch_and_add(&scope->refcount, 1); + } + return scope; +} + +void +sentry__scope_decref(sentry_scope_t *scope) +{ + if (!scope) { + return; + } + + if (scope == &g_scope) { + SENTRY__MUTEX_INIT_DYN_ONCE(g_lock); + sentry__mutex_lock(&g_lock); + long refcount = sentry__atomic_fetch_and_add(&scope->refcount, -1); + assert(refcount > 1); + if (refcount == 2 && g_scope_idle_initialized) { + sentry__cond_wake(&g_scope_idle); + } + sentry__mutex_unlock(&g_lock); + return; + } + + if (sentry__atomic_fetch_and_add(&scope->refcount, -1) != 1) { + return; + } + cleanup_scope(scope); + sentry_free(scope); +} + void sentry__scope_cleanup(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; + } + while (g_scope_initialized && sentry__atomic_fetch(&g_scope.refcount) > 1) { + sentry__cond_wait(&g_scope_idle, &g_lock); + } if (g_scope_initialized) { g_scope_initialized = false; - cleanup_scope(&g_scope); + 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); } sentry_scope_t * -sentry__scope_lock(void) +sentry__scope_getref(void) { SENTRY__MUTEX_INIT_DYN_ONCE(g_lock); sentry__mutex_lock(&g_lock); - return get_scope(); + if (!g_scope_idle_initialized) { + sentry__cond_init(&g_scope_idle); + g_scope_idle_initialized = true; + } + sentry_scope_t *scope = sentry__scope_incref(get_scope()); + sentry__mutex_unlock(&g_lock); + return scope; } -static void -unlock_scope(bool flush) +void +sentry__scope_finish(sentry_scope_t *scope, bool flush) { - SENTRY__MUTEX_INIT_DYN_ONCE(g_lock); + if (!scope) { + return; + } - if (g_scope.is_notifying > 0) { + sentry__mutex_lock(&scope->observers_lock); + if (scope->is_notifying > 0) { // defer the flush requested by a reentrant scope change - g_scope.pending_flush = flush || g_scope.pending_flush; + scope->pending_flush = flush || scope->pending_flush; flush = false; } else { // consume any flush requested by a reentrant scope change - flush = flush || g_scope.pending_flush; - g_scope.pending_flush = false; + flush = flush || scope->pending_flush; + scope->pending_flush = false; } + sentry__mutex_unlock(&scope->observers_lock); + + sentry__scope_decref(scope); - // 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) { @@ -191,18 +497,6 @@ unlock_scope(bool flush) } } -void -sentry__scope_unlock(void) -{ - unlock_scope(false); -} - -void -sentry__scope_flush_unlock(void) -{ - unlock_scope(true); -} - sentry_scope_observer_t * sentry__scope_observer_new(void) { @@ -217,10 +511,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; } @@ -232,6 +528,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; } @@ -239,7 +536,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; } @@ -252,6 +555,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++) { @@ -262,13 +566,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; } @@ -277,9 +584,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; } @@ -296,6 +605,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 * @@ -306,25 +616,38 @@ sentry_scope_new(void) return NULL; } - init_scope(scope); + if (!init_scope(scope, NULL)) { + sentry_free(scope); + return NULL; + } return scope; } void sentry_scope_free(sentry_scope_t *scope) +{ + sentry__scope_decref(scope); +} + +bool +sentry__scope_is_one_shot(const sentry_scope_t *scope) +{ + return scope && scope->one_shot; +} + +void +sentry__scope_set_one_shot(sentry_scope_t *scope, bool one_shot) { if (!scope) { return; } - - cleanup_scope(scope); - sentry_free(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); } } @@ -334,11 +657,50 @@ 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; } +void +sentry__scope_apply_options(sentry_scope_t *scope, sentry_options_t *options) +{ + sentry_scope_data_t *data = scope->data; + SENTRY_SCOPE_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); + } + 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)); + sentry__scope_update_dsc(scope, options); +} + void sentry_scope_clear(sentry_scope_t *scope) { @@ -355,35 +717,7 @@ 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 = scope->trace_managed; - 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; - - cleanup_scope(scope); - init_scope(scope); - - 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; - scope->trace_managed = trace_managed; - scope->one_shot = one_shot; - scope->observers = observers; - scope->num_observers = num_observers; - scope->is_notifying = is_notifying; - scope->pending_flush = pending_flush; + clear_scope_data(scope->data); } sentry_scope_t * @@ -398,79 +732,118 @@ sentry_scope_clone(const sentry_scope_t *scope) return NULL; } - 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); - 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 = scope->level; - clone->last_event_id = scope->last_event_id; - 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 = scope->trace_managed; + sentry_scope_data_t *data = clone_scope_data(scope->data); + if (!data) { + sentry_free(clone); + return NULL; + } + if (!init_scope(clone, data)) { + free_scope_data(data); + sentry_free(clone); + return NULL; + } return clone; } -void -sentry__scope_freeze_dsc(sentry_scope_t *scope, sentry_value_t incoming) +sentry_value_t +sentry__scope_load_propagation_context(const sentry_scope_t *scope) { - sentry_value_decref(scope->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; + sentry_value_t propagation_context = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + propagation_context + = sentry__value_clone(scope->data->propagation_context); + } + return propagation_context; } void -sentry__scope_update_dsc(sentry_scope_t *scope, const sentry_options_t *options) +sentry__scope_set_propagation_context( + sentry_scope_t *scope, const char *key, sentry_value_t value) { - sentry_value_decref(scope->dynamic_sampling_context); - sentry_value_t dsc = sentry_value_new_object(); + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + sentry_value_set_by_key(scope->data->propagation_context, key, value); + } +} - if (options->dsn) { - sentry_value_set_by_key(dsc, "public_key", - sentry_value_new_string(options->dsn->public_key)); +void +sentry__scope_regenerate_propagation_context(sentry_scope_t *scope) +{ + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + generate_propagation_context(scope->data->propagation_context); } - 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_t +sentry__scope_load_trace_context(const sentry_scope_t *scope) +{ + sentry_value_t trace_context = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + trace_context = sentry__value_clone( + sentry_value_get_by_key(scope->data->propagation_context, "trace")); } - sentry_value_set_by_key(dsc, "sample_rate", - sentry_value_new_double(options->traces_sample_rate)); - if (options->traces_sampler) { + return trace_context; +} + +void +sentry__scope_set_trace_context( + sentry_scope_t *scope, const char *key, sentry_value_t value) +{ + SENTRY_SCOPE_WRITE_LOCK (scope->data) { sentry_value_set_by_key( - dsc, "sample_rate", sentry_value_new_double(1.0)); + sentry_value_get_by_key(scope->data->propagation_context, "trace"), + key, value); } - sentry_value_t sample_rand = sentry_value_get_by_key( - sentry_value_get_by_key(scope->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_value_new_string(scope->release)); - sentry_value_set_by_key( - dsc, "environment", sentry_value_new_string(scope->environment)); +} + +bool +sentry__scope_is_trace_managed(const sentry_scope_t *scope) +{ + bool managed = false; + SENTRY_SCOPE_READ_LOCK (scope->data) { + managed = scope->data->trace_managed; + } + return managed; +} + +void +sentry__scope_set_trace_managed(sentry_scope_t *scope, bool managed) +{ + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + scope->data->trace_managed = managed; + } +} + +sentry_value_t +sentry__scope_load_dsc(const sentry_scope_t *scope) +{ + sentry_value_t dsc = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + dsc = sentry__value_clone(scope->data->dynamic_sampling_context); + } + return dsc; +} + +void +sentry__scope_foreach_dsc(const sentry_scope_t *scope, + void (*callback)(const char *key, sentry_value_t value, void *userdata), + void *userdata) +{ + SENTRY_SCOPE_READ_LOCK (scope->data) { + sentry__value_foreach_key_value( + scope->data->dynamic_sampling_context, callback, userdata); + } +} - scope->dynamic_sampling_context = dsc; +void +sentry__scope_freeze_dsc(sentry_scope_t *scope, sentry_value_t incoming) +{ + sentry_value_t dsc = sentry_value_new_object(); + sentry__value_merge_objects(dsc, incoming); + sentry_value_freeze(dsc); + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + sentry__value_replace(&scope->data->dynamic_sampling_context, dsc); + } } #if !defined(SENTRY_PLATFORM_NX) @@ -576,27 +949,14 @@ sentry__symbolize_stacktrace(sentry_value_t stacktrace) } #endif -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; - } else { - return sentry_value_new_null(); - } -} - #ifdef SENTRY_UNITTEST -sentry_value_t -sentry__scope_get_span_or_transaction(void) +bool +sentry__scope_has_observers(const sentry_scope_t *scope) { - sentry_value_t result = sentry_value_new_null(); - SENTRY_WITH_SCOPE (scope) { - result = get_span_or_transaction(scope); - } - return result; + 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 @@ -613,6 +973,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)) { \ @@ -629,55 +995,81 @@ 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")) { - 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) { + 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); - PLACE_STRING("transaction", scope->transaction); - PLACE_VALUE("sdk", scope->client_sdk); + sentry_value_decref(user); - 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); - } - } 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 fingerprint = sentry__scope_ref_fingerprint(scope); + PLACE_CLONED_VALUE("fingerprint", fingerprint); + sentry_value_decref(fingerprint); + + sentry_value_t transaction = sentry__scope_ref_transaction(scope); + PLACE_STRING_VALUE("transaction", transaction); + sentry_value_decref(transaction); + + sentry_value_t client_sdk = sentry__scope_ref_client_sdk(scope); + PLACE_VALUE("sdk", client_sdk); + sentry_value_decref(client_sdk); + + SENTRY_SCOPE_READ_LOCK (scope->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(scope->data->tags)) { + sentry_value_set_by_key( + event, "tags", sentry__value_clone(scope->data->tags)); + } + } else { + sentry__value_merge_objects(event_tags, scope->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(scope->data->extra)) { + sentry_value_set_by_key( + event, "extra", sentry__value_clone(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(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + contexts = sentry__value_clone(scope->data->contexts); + } if (is_transaction && !sentry_value_is_null(contexts)) { sentry_value_remove_by_key(contexts, "trace"); } @@ -687,7 +1079,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 = sentry__scope_ref_span_or_transaction(scope); scope_trace = sentry__value_get_trace_context(scoped_txn_or_span); } if (!sentry_value_is_null(scope_trace)) { @@ -703,6 +1095,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"); @@ -710,7 +1103,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->propagation_context); + sentry_value_t propagation_context + = sentry__scope_load_propagation_context(scope); + sentry__value_merge_objects(contexts, propagation_context); + sentry_value_decref(propagation_context); } if (sentry_value_is_null(event_contexts)) { PLACE_VALUE("contexts", contexts); @@ -723,7 +1119,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__scope_breadcrumbs_to_list(scope); sentry_value_set_by_key(event, "breadcrumbs", sentry__value_merge_breadcrumbs(event_breadcrumbs, scope_breadcrumbs, options->max_breadcrumbs)); @@ -747,6 +1143,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 @@ -755,25 +1152,68 @@ 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) { - SENTRY_SCOPE_NOTIFY(scope, add_breadcrumb, breadcrumb); + bool added = false; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + added = sentry__ringbuffer_append(scope->data->breadcrumbs, breadcrumb) + == 0; + if (added) { + sentry_value_incref(breadcrumb); + } + } + if (added) { + SENTRY_SCOPE_NOTIFY_OWNED(scope, add_breadcrumb, breadcrumb); + } +} + +sentry_value_t +sentry__scope_breadcrumbs_to_list(const sentry_scope_t *scope) +{ + sentry_value_t breadcrumbs = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + breadcrumbs = sentry__ringbuffer_to_list(scope->data->breadcrumbs); } + return breadcrumbs; +} + +sentry_value_t +sentry__scope_ref_user(const sentry_scope_t *scope) +{ + sentry_value_t user = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + user = sentry_value_incref(scope->data->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_SCOPE_WRITE_LOCK (scope->data) { + sentry__value_replace(&scope->data->user, sentry_value_incref(user)); + } + SENTRY_SCOPE_NOTIFY_OWNED(scope, set_user, user); +} + +sentry_value_t +sentry__scope_load_tags(const sentry_scope_t *scope) +{ + sentry_value_t tags = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + tags = sentry__value_clone(scope->data->tags); + } + return 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) { + sentry_value_t tag_value = sentry_value_new_string(value); + bool did_set = false; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + did_set + = sentry_value_set_by_key(scope->data->tags, key, tag_value) == 0; + } + if (did_set) { SENTRY_SCOPE_NOTIFY(scope, set_tag, key, value); } } @@ -782,11 +1222,26 @@ 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; } + + sentry_value_t stored_value = sentry_value_incref(tag_value); + bool did_set = false; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + did_set = sentry_value_set_by_key_n( + scope->data->tags, key, key_len, stored_value) + == 0; + } + if (did_set) { + 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 @@ -807,23 +1262,108 @@ 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) +{ + bool removed = false; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + removed = sentry_value_remove_by_key(scope->data->tags, key) == 0; + } + if (removed) { + 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 *removed_key = NULL; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + removed_key = sentry__value_remove_and_take_key_n( + scope->data->tags, key, key_len); + } + if (removed_key) { + SENTRY_SCOPE_NOTIFY(scope, remove_tag, removed_key); + } + sentry_free(removed_key); +} + +sentry_value_t +sentry__scope_load_extra(const sentry_scope_t *scope) +{ + sentry_value_t extra = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + extra = sentry__value_clone(scope->data->extra); + } + return 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) { + sentry_value_t stored_value = sentry_value_incref(value); + bool did_set = false; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + did_set = sentry_value_set_by_key(scope->data->extra, key, stored_value) + == 0; + } + if (did_set) { 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; + } + + sentry_value_t stored_value = sentry_value_incref(value); + bool did_set = false; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + did_set = sentry_value_set_by_key_n( + scope->data->extra, key, key_len, stored_value) + == 0; + } + if (did_set) { + 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) +{ + bool removed = false; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + removed = sentry_value_remove_by_key(scope->data->extra, key) == 0; + } + if (removed) { + 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 *removed_key = NULL; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + removed_key = sentry__value_remove_and_take_key_n( + scope->data->extra, key, key_len); + } + if (removed_key) { + SENTRY_SCOPE_NOTIFY(scope, remove_extra, removed_key); } + sentry_free(removed_key); } void @@ -844,40 +1384,88 @@ 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_SCOPE_WRITE_LOCK (scope->data) { + sentry_value_set_by_key_n( + scope->data->attributes, key, key_len, attribute); + } +} + +sentry_value_t +sentry__scope_load_attributes(const sentry_scope_t *scope) +{ + sentry_value_t attributes = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + attributes = sentry__value_clone(scope->data->attributes); + } + return attributes; } void sentry_scope_remove_attribute(sentry_scope_t *scope, const char *key) { - sentry_value_remove_by_key(scope->attributes, key); + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + 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_SCOPE_WRITE_LOCK (scope->data) { + sentry_value_remove_by_key_n(scope->data->attributes, key, key_len); + } +} + +sentry_value_t +sentry__scope_load_contexts(const sentry_scope_t *scope) +{ + sentry_value_t contexts = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + contexts = sentry__value_clone(scope->data->contexts); + } + return 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) { + sentry_value_t stored_value = sentry_value_incref(value); + bool did_set = false; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + did_set + = sentry_value_set_by_key(scope->data->contexts, key, stored_value) + == 0; + } + if (did_set) { 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; } + + sentry_value_t stored_value = sentry_value_incref(value); + bool did_set = false; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + did_set = sentry_value_set_by_key_n( + scope->data->contexts, key, key_len, stored_value) + == 0; + } + if (did_set) { + SENTRY_SCOPE_NOTIFY(scope, set_context, notify_key, value); + } + sentry_free(notify_key); + sentry_value_decref(value); } void @@ -892,22 +1480,66 @@ 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_value_t stored_value = sentry_value_incref(value); + bool did_set = false; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + sentry_value_t context + = sentry_value_get_by_key_n(scope->data->contexts, key, key_len); + if (!sentry_value_is_null(context)) { + sentry__value_merge_objects(stored_value, context); } + did_set = sentry_value_set_by_key_n( + scope->data->contexts, key, key_len, stored_value) + == 0; + } + if (did_set) { + 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) +{ + bool removed = false; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + removed = sentry_value_remove_by_key(scope->data->contexts, key) == 0; + } + if (removed) { + SENTRY_SCOPE_NOTIFY(scope, remove_context, key); } - SENTRY_SCOPE_NOTIFY(scope, set_context, k, value); +} + +void +sentry__scope_remove_context_n( + sentry_scope_t *scope, const char *key, size_t key_len) +{ + char *removed_key = NULL; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + removed_key = sentry__value_remove_and_take_key_n( + scope->data->contexts, key, key_len); + } + if (removed_key) { + SENTRY_SCOPE_NOTIFY(scope, remove_context, removed_key); + } + sentry_free(removed_key); +} + +sentry_value_t +sentry__scope_ref_fingerprint(const sentry_scope_t *scope) +{ + sentry_value_t fingerprint = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + fingerprint = sentry_value_incref(scope->data->fingerprint); + } + return fingerprint; } void @@ -920,9 +1552,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 @@ -975,23 +1605,50 @@ sentry_scope_set_fingerprints( return; } - sentry_value_decref(scope->fingerprint); - scope->fingerprint = fingerprints; - SENTRY_SCOPE_NOTIFY(scope, set_fingerprint, fingerprints); + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + sentry__value_replace( + &scope->data->fingerprint, sentry_value_incref(fingerprints)); + } + SENTRY_SCOPE_NOTIFY_OWNED(scope, set_fingerprint, fingerprints); } 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_SCOPE_WRITE_LOCK (scope->data) { + sentry__value_replace( + &scope->data->fingerprint, sentry_value_incref(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; + SENTRY_SCOPE_READ_LOCK (scope->data) { + level = scope->data->level; + } + return level; +} + +sentry_value_t +sentry__scope_ref_client_sdk(const sentry_scope_t *scope) +{ + sentry_value_t client_sdk = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + client_sdk = sentry_value_incref(scope->data->client_sdk); + } + return client_sdk; } void sentry_scope_set_level(sentry_scope_t *scope, sentry_level_t level) { - scope->level = level; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + scope->data->level = level; + } SENTRY_SCOPE_NOTIFY(scope, set_level, level); } @@ -999,38 +1656,65 @@ 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 +sentry__scope_load_attachments(const sentry_scope_t *scope) +{ + sentry_value_t attachments = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + attachments = sentry__value_clone(scope->data->attachments); + } + return 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); - sentry_value_t added - = sentry__attachments_add(&scope->attachments, attachment); - if (!sentry_value_is_null(added) - && sentry_value_get_length(scope->attachments) > len) { + bool did_add = false; + sentry_value_t added = sentry_value_new_null(); + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + size_t len = sentry_value_get_length(scope->data->attachments); + added = sentry__attachments_add(&scope->data->attachments, attachment); + did_add = !sentry_value_is_null(added) + && sentry_value_get_length(scope->data->attachments) > len; + } + if (did_add) { SENTRY_SCOPE_NOTIFY(scope, add_attachment, added); } return added; } +sentry_value_t +sentry__scope_take_attachments(sentry_scope_t *scope) +{ + sentry_value_t attachments = sentry_value_new_null(); + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + 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) +{ + sentry_value_t removed = sentry_value_new_null(); + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + removed = sentry__attachments_remove( + scope->data->attachments, attachment_id); + } + return removed; +} + sentry_uuid_t sentry_scope_add_attachment(sentry_scope_t *scope, sentry_value_t attachment) { @@ -1045,6 +1729,240 @@ sentry_scope_add_attachment(sentry_scope_t *scope, sentry_value_t attachment) return attachment_id; } +sentry_transaction_t * +sentry__scope_ref_transaction_object(const sentry_scope_t *scope) +{ + sentry_transaction_t *transaction = NULL; + SENTRY_SCOPE_READ_LOCK (scope->data) { + transaction = scope->data->transaction_object; + sentry__transaction_incref(transaction); + } + return transaction; +} + +void +sentry__scope_set_transaction_object( + sentry_scope_t *scope, sentry_transaction_t *transaction) +{ + sentry__transaction_incref(transaction); + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + 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) +{ + bool removed = false; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + if (transaction && scope->data->transaction_object == transaction) { + scope->data->transaction_object = NULL; + removed = true; + } + } + if (removed) { + sentry__transaction_decref(transaction); + } + return removed; +} + +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")); + sentry_transaction_t *transaction_object = NULL; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + if (scope->data->transaction_object + && value_has_span_id( + scope->data->transaction_object->inner, span_id)) { + transaction_object = scope->data->transaction_object; + scope->data->transaction_object = NULL; + } + } + sentry__transaction_decref(transaction_object); + return transaction_object != NULL; +} + +bool +sentry__scope_restore_transaction_object( + sentry_scope_t *scope, sentry_transaction_t *transaction) +{ + bool restored = false; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + if (!scope->data->transaction_object && !scope->data->span + && transaction) { + scope->data->transaction_object = transaction; + restored = true; + } + } + return restored; +} + +sentry_span_t * +sentry__scope_ref_span(const sentry_scope_t *scope) +{ + sentry_span_t *span = NULL; + SENTRY_SCOPE_READ_LOCK (scope->data) { + span = scope->data->span; + sentry__span_incref(span); + } + return span; +} + +sentry_value_t +sentry__scope_ref_span_or_transaction(const sentry_scope_t *scope) +{ + sentry_value_t value = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + if (scope->data->span) { + value = sentry_value_incref(scope->data->span->inner); + } else if (scope->data->transaction_object) { + value = sentry_value_incref(scope->data->transaction_object->inner); + } + } + return value; +} + +void +sentry__scope_set_span(sentry_scope_t *scope, sentry_span_t *span) +{ + sentry__span_incref(span); + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + 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) +{ + bool removed = false; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + if (span && scope->data->span == span) { + scope->data->span = NULL; + removed = true; + } + } + if (removed) { + sentry__span_decref(span); + } + return removed; +} + +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")); + sentry_span_t *scope_span = NULL; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + if (scope->data->span + && value_has_span_id(scope->data->span->inner, span_id)) { + scope_span = scope->data->span; + scope->data->span = NULL; + } + } + sentry__span_decref(scope_span); + return scope_span != NULL; +} + +bool +sentry__scope_restore_span(sentry_scope_t *scope, sentry_span_t *span) +{ + bool restored = false; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + if (!scope->data->span && !scope->data->transaction_object && span) { + scope->data->span = span; + restored = true; + } + } + return restored; +} + +sentry_value_t +sentry__scope_ref_release(const sentry_scope_t *scope) +{ + sentry_value_t release = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + release = sentry_value_incref(scope->data->release); + } + return release; +} + +void +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); + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + 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); +} + +sentry_value_t +sentry__scope_ref_environment(const sentry_scope_t *scope) +{ + sentry_value_t environment = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + environment = sentry_value_incref(scope->data->environment); + } + return environment; +} + +void +sentry__scope_set_environment_n( + sentry_scope_t *scope, const char *environment, size_t environment_len) +{ + sentry_value_t value + = sentry_value_new_string_n(environment, environment_len); + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + 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); +} + +sentry_value_t +sentry__scope_ref_transaction(const sentry_scope_t *scope) +{ + sentry_value_t transaction = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + transaction = sentry_value_incref(scope->data->transaction); + } + return transaction; +} + +void +sentry__scope_set_transaction_n( + sentry_scope_t *scope, const char *transaction, size_t transaction_len) +{ + sentry_value_t value + = sentry_value_new_string_n(transaction, transaction_len); + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + 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); + } + } + SENTRY_SCOPE_NOTIFY_OWNED(scope, set_transaction, value); +} + sentry_uuid_t sentry_scope_attach_file(sentry_scope_t *scope, const char *path) { @@ -1114,47 +2032,77 @@ 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); - - // 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"), - "trace_id"); - - sentry_value_t parent_span_id = sentry_value_new_object(); - if (scope->transaction_object) { - sentry_value_t span_id = sentry_value_get_by_key( - scope->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) { - sentry_value_t span_id - = sentry_value_get_by_key(scope->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"); - } - sentry_value_set_by_key( - parent_span_id, "type", sentry_value_new_string("string")); - if ((scope->transaction_object || scope->span) - && sentry_value_is_null(sentry_value_get_by_key( - attributes, "sentry.trace.parent_span_id"))) { + const sentry_scope_data_t *data = scope->data; + sentry_value_t os_name = sentry_value_new_null(); + sentry_value_t os_version = sentry_value_new_null(); + + SENTRY_SCOPE_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( - attributes, "sentry.trace.parent_span_id", parent_span_id); + 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(parent_span_id); + sentry_value_decref(os_name); } - 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); + if (!sentry_value_is_null(os_version)) { + sentry__value_add_attribute( + attributes, os_version, "string", "os.version"); + } else { + sentry_value_decref(os_version); } - 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( @@ -1162,53 +2110,57 @@ 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_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"); - } - } - if (scope->environment) { + sentry_value_decref(user); + + 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 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(); + if (scope) { + SENTRY_SCOPE_READ_LOCK (scope->data) { + event_id = scope->data->last_event_id; + } + } + return event_id; +} + +void +sentry__scope_set_last_event_id(sentry_scope_t *scope, sentry_uuid_t event_id) +{ + if (scope) { + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + scope->data->last_event_id = event_id; + } + } } void @@ -1218,7 +2170,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 aa758a824b..4f38dcfdbc 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -6,13 +6,14 @@ #include "sentry_attachment.h" #include "sentry_ringbuffer.h" #include "sentry_session.h" +#include "sentry_sync.h" #include "sentry_value.h" /** * 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 @@ -27,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); @@ -49,39 +50,16 @@ 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 { - char *release; - char *environment; - char *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; + long refcount; + sentry_scope_data_t *data; + sentry_mutex_t observers_lock; sentry_scope_observer_t **observers; size_t num_observers; size_t is_notifying; @@ -109,31 +87,43 @@ typedef enum { } sentry_scope_mode_t; /** - * This will acquire a lock on the global scope. + * This will return a new reference to the global scope, initializing it if + * needed. + */ +sentry_scope_t *sentry__scope_getref(void); + +/** + * Increment the refcount and return the scope pointer. */ -sentry_scope_t *sentry__scope_lock(void); +sentry_scope_t *sentry__scope_incref(sentry_scope_t *scope); /** - * Release the lock on the global scope. + * Decrement the refcount and free the scope when the last reference is + * released. */ -void sentry__scope_unlock(void); +void sentry__scope_decref(sentry_scope_t *scope); /** * This will free all the data attached to the global scope */ 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. */ void sentry__scope_free_one_shot(sentry_scope_t *scope); /** - * This will notify any backend of scope changes. - * This function must be called while holding the scope lock, and it will be - * unlocked internally. + * Finish a global scope access, optionally notifying the backend of changes. + * This consumes the caller's scope reference. */ -void sentry__scope_flush_unlock(void); +void sentry__scope_finish(sentry_scope_t *scope, bool flush); /** * This will merge the requested data which is in the given `scope` to the given @@ -145,27 +135,105 @@ 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); +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); + +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); + +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); + +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_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); + +/** + * 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_load_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_load_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_load_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_load_attributes(const sentry_scope_t *scope); + +sentry_value_t sentry__scope_load_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_load_propagation_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_load_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); + +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); +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_ref_span(const sentry_scope_t *scope); +sentry_value_t sentry__scope_ref_span_or_transaction( + 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 - * 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_getref(); Scope; \ + sentry__scope_finish((sentry_scope_t *)Scope, 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_getref(); Scope; \ + sentry__scope_finish(Scope, 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_getref(); Scope; \ + sentry__scope_finish(Scope, false), Scope = NULL) /** * Allocate and zero-initialize a scope observer. @@ -180,8 +248,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); @@ -189,8 +257,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); @@ -211,6 +279,11 @@ void sentry__scope_end_notify(sentry_scope_t *scope); sentry__scope_end_notify(scope); \ } while (0) +sentry_value_t sentry__scope_load_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 * and the current propagation context. The previous DSC is discarded. @@ -234,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. */ @@ -245,5 +321,5 @@ 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); #endif diff --git a/src/sentry_session.c b/src/sentry_session.c index 182dfa92c5..267365f949 100644 --- a/src/sentry_session.c +++ b/src/sentry_session.c @@ -50,8 +50,17 @@ 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); + 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); @@ -219,8 +228,10 @@ sentry_start_session(void) if (options) { options->session = sentry__session_new(scope); if (options->session) { - sentry__session_sync_user(options->session, scope->user, + 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/src/sentry_sync.h b/src/sentry_sync.h index 01ac4478b9..4a8b89bc82 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 @@ -198,6 +199,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), \ @@ -278,6 +410,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 @@ -359,6 +492,74 @@ 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 +# 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; \ + 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) +# endif + +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 { \ diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c index 8b94d36e9f..e383b23a32 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -96,23 +96,20 @@ 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_load_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"))); } + sentry_value_decref(trace_context); } return transaction_context; @@ -964,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( - scope->dynamic_sampling_context, append_baggage_member, &sb); + sentry__scope_foreach_dsc(scope, append_baggage_member, &sb); } char *baggage = sentry__stringbuilder_into_string(&sb); @@ -1019,14 +1015,8 @@ 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; - } - if (scope->transaction_object) { - sentry__transaction_incref(scope->transaction_object); - s.saved_tx_obj = scope->transaction_object; - } + 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 @@ -1041,12 +1031,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/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/src/sentry_value.c b/src/sentry_value.c index 2267643e91..626cd9b725 100644 --- a/src/sentry_value.c +++ b/src/sentry_value.c @@ -449,6 +449,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_attachments.c b/tests/unit/test_attachments.c index 066e8de8a2..b267a477ed 100644 --- a/tests/unit/test_attachments.c +++ b/tests/unit/test_attachments.c @@ -17,18 +17,18 @@ static void add_scope_attachments(sentry_envelope_t *envelope) { SENTRY_WITH_SCOPE (scope) { - sentry__envelope_add_attachments(envelope, scope->attachments, NULL); + sentry_value_t attachments = sentry__scope_load_attachments(scope); + sentry__envelope_add_attachments(envelope, attachments, NULL); + sentry_value_decref(attachments); } } static void -count_backend_attachment(sentry_backend_t *backend, sentry_value_t attachment, - const sentry_options_t *options) +count_backend_attachment(void *data, sentry_value_t attachment) { - size_t *count = (size_t *)backend->data; + size_t *count = (size_t *)data; (*count)++; - TEST_CHECK(!sentry_value_is_frozen(attachment)); - TEST_CHECK(!!options); + TEST_CHECK(sentry_value_is_frozen(attachment)); } SENTRY_TEST(attachment_placeholder) @@ -147,8 +147,10 @@ SENTRY_TEST(attachments_add_dedupe) size_t backend_add_count = 0; sentry_backend_t *backend = SENTRY_MAKE(sentry_backend_t); TEST_ASSERT(!!backend); - backend->data = &backend_add_count; - backend->add_attachment_func = count_backend_attachment; + backend->scope_observer = sentry__scope_observer_new(); + TEST_ASSERT(!!backend->scope_observer); + backend->scope_observer->data = &backend_add_count; + backend->scope_observer->add_attachment = count_backend_attachment; sentry_options_set_backend(options, backend); sentry_options_add_attachment(options, SENTRY_TEST_PATH_PREFIX ".a.txt"); sentry_options_add_attachment(options, SENTRY_TEST_PATH_PREFIX ".b.txt"); @@ -388,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_load_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_basic.c b/tests/unit/test_basic.c index b08f2551ab..b0bc0971ce 100644 --- a/tests/unit/test_basic.c +++ b/tests/unit/test_basic.c @@ -424,8 +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(scope->client_sdk, "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( @@ -449,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(); diff --git a/tests/unit/test_concurrency.c b/tests/unit/test_concurrency.c index cdc3c739aa..e0bdd4133b 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_getref(); + + 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_finish(scope, 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/test_logs.c b/tests/unit/test_logs.c index 0c122bffce..527b63c0ed 100644 --- a/tests/unit/test_logs.c +++ b/tests/unit/test_logs.c @@ -765,12 +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_load_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_value_get_by_key( - global_scope->propagation_context, "trace"), - "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_scope.c b/tests/unit/test_scope.c index 64d87d5f53..bfbe08c29e 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_getter_t)(const sentry_scope_t *scope); + +static sentry_value_t +scope_value_get_by_key( + scope_value_getter_t get, const sentry_scope_t *scope, const char *key) +{ + sentry_value_t values = get(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_getter_t get, const sentry_scope_t *scope) +{ + sentry_value_t value = get(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(scope->contexts, "device"); + sentry_value_t ctx = scope_value_get_by_key( + sentry__scope_load_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(scope->contexts, "device"); + sentry_value_t ctx = scope_value_get_by_key( + sentry__scope_load_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(local_scope->contexts, "os"); + sentry_value_t ctx = scope_value_get_by_key( + sentry__scope_load_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,13 +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(local_scope->contexts, "os"); + ctx = scope_value_get_by_key( + sentry__scope_load_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); } @@ -443,6 +470,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); } @@ -660,6 +697,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); } @@ -1095,28 +1139,34 @@ 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"); + = scope_value_get_by_key(sentry__scope_load_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(scope->extra, "data"); + = scope_value_get_by_key(sentry__scope_load_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( - scope->user, "username")), + scope_user, "username")), "original"); TEST_CHECK(sentry_value_is_null( - sentry_value_get_by_key(scope->user, "injected"))); + sentry_value_get_by_key(scope_user, "injected"))); + sentry_value_decref(scope_user); - TEST_CHECK_INT_EQUAL(sentry_value_get_length(scope->fingerprint), 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(); @@ -1133,7 +1183,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_load_attributes(scope); sentry_value_t retrieved_attr = sentry_value_get_by_key(attributes, "valid_key"); @@ -1145,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 @@ -1155,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 = scope->attributes; + sentry_value_t attributes = sentry__scope_load_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' @@ -1171,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 = scope->attributes; + sentry_value_t attributes = sentry__scope_load_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 = scope->attributes; + sentry_value_t attributes = sentry__scope_load_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 @@ -1197,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 = scope->attributes; + sentry_value_t attributes = sentry__scope_load_attributes(scope); sentry_value_t retrieved_attr = sentry_value_get_by_key(attributes, "key_n"); @@ -1212,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(); @@ -1232,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 = global_scope->attributes; + sentry_value_t attributes = sentry__scope_load_attributes(global_scope); // Verify global attributes are set TEST_CHECK_STRING_EQUAL( @@ -1247,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) { @@ -1260,7 +1316,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_load_attributes(local_scope); // Verify local attributes are set TEST_CHECK_STRING_EQUAL( @@ -1277,7 +1334,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_load_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")), @@ -1286,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); } @@ -1294,7 +1354,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_load_attributes(scope); TEST_CHECK( sentry_value_is_null(sentry_value_get_by_key(attributes, "all"))); // Other attributes should still exist @@ -1302,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 @@ -1310,7 +1371,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_load_attributes(local_scope); sentry_value_t attr = sentry_value_get_by_key(local_attributes, "test_key"); @@ -1324,10 +1386,14 @@ SENTRY_TEST(scope_local_attributes) sentry_value_as_string(sentry_value_get_by_key(attr, "unit")), "percent"); + sentry_value_decref(local_attributes); + // Remove using _n variant sentry_scope_remove_attribute_n(local_scope, "test_key", 8); + local_attributes = sentry__scope_load_attributes(local_scope); 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); } @@ -1342,9 +1408,11 @@ 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_load_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); } @@ -1367,6 +1435,7 @@ typedef struct { bool was_called; bool was_cleared; size_t set_tag_count; + sentry_scope_t *scope; } test_observer_data_t; typedef struct { @@ -1397,10 +1466,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; } @@ -1419,18 +1489,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; } @@ -1499,6 +1571,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; } @@ -1507,6 +1583,12 @@ static void observe_add_breadcrumb(void *data, sentry_value_t breadcrumb) { test_observer_data_t *d = (test_observer_data_t *)data; + if (d->scope) { + sentry_scope_t *scope = d->scope; + d->scope = NULL; + sentry_scope_add_breadcrumb( + scope, sentry_value_new_breadcrumb(NULL, "replacement")); + } if (sentry_value_is_null(d->breadcrumbs)) { d->breadcrumbs = sentry_value_new_list(); } @@ -1646,8 +1728,8 @@ 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__scope_has_observers(scope)); + TEST_CHECK(scope_value_get_length(sentry__scope_load_tags, scope) == 0); d.was_called = false; sentry_scope_set_tag(scope, "after", "clear"); @@ -1673,9 +1755,8 @@ 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_value_get_length(scope->tags) == 0); + TEST_CHECK(sentry__scope_has_observers(scope)); + TEST_CHECK(scope_value_get_length(sentry__scope_load_tags, scope) == 0); sentry_value_decref(observer_data.tags); sentry_scope_free(scope); @@ -1688,12 +1769,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() }; @@ -1751,8 +1830,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"); @@ -1764,8 +1842,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); @@ -1830,14 +1907,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); @@ -1896,6 +1972,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(); } @@ -1918,6 +2003,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(); } @@ -1941,6 +2035,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(); } @@ -2038,6 +2143,7 @@ SENTRY_TEST(scope_observer_user) SENTRY_TEST(scope_observer_breadcrumbs) { SENTRY_TEST_OPTIONS_NEW(options); + sentry_options_set_max_breadcrumbs(options, 1); sentry_init(options); test_observer_data_t d = { .breadcrumbs = sentry_value_new_null() }; @@ -2046,28 +2152,33 @@ SENTRY_TEST(scope_observer_breadcrumbs) observer->add_breadcrumb = observe_add_breadcrumb; SENTRY_WITH_SCOPE_MUT (scope) { + d.scope = scope; sentry__scope_add_observer(scope, observer); } sentry_add_breadcrumb( sentry_value_new_breadcrumb(NULL, "first breadcrumb")); TEST_CHECK(d.was_called); - TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.breadcrumbs), 1); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.breadcrumbs), 2); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key( sentry_value_get_by_index(d.breadcrumbs, 0), "message")), + "replacement"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key( + sentry_value_get_by_index(d.breadcrumbs, 1), "message")), "first breadcrumb"); sentry_add_breadcrumb( sentry_value_new_breadcrumb("warning", "second breadcrumb")); - TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.breadcrumbs), 2); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.breadcrumbs), 3); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key( - sentry_value_get_by_index(d.breadcrumbs, 1), "message")), + sentry_value_get_by_index(d.breadcrumbs, 2), "message")), "second breadcrumb"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key( - sentry_value_get_by_index(d.breadcrumbs, 1), "type")), + sentry_value_get_by_index(d.breadcrumbs, 2), "type")), "warning"); sentry_value_decref(d.breadcrumbs); @@ -2398,7 +2509,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( + scope_value_get_length(sentry__scope_load_attributes, scope), 0); sentry_scope_free(scope); } @@ -2415,7 +2527,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( + scope_value_get_length(sentry__scope_load_attributes, scope), 0); sentry_scope_free(scope); } @@ -2425,18 +2538,25 @@ 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_t *retained_scope = sentry__scope_incref(user_scope); sentry_scope_free(user_scope); + sentry_scope_set_tag(retained_scope, "retained", "true"); + sentry_value_t retained_tag = scope_value_get_by_key( + sentry__scope_load_tags, retained_scope, "retained"); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(retained_tag), "true"); + sentry_value_decref(retained_tag); + sentry__scope_decref(retained_scope); } 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__scope_breadcrumbs_to_list(scope); size_t count = sentry_value_get_length(breadcrumbs); sentry_value_decref(breadcrumbs); return count; @@ -2455,13 +2575,14 @@ 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(clone->tags, "shared")), - "original"); - TEST_CHECK(clone->level == SENTRY_LEVEL_WARNING); + sentry_value_t clone_tag + = scope_value_get_by_key(sentry__scope_load_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); // Mutating the clone does not affect the source, and vice versa. @@ -2470,11 +2591,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(scope->tags, "shared")), - "original"); - TEST_CHECK(sentry_value_is_null( - sentry_value_get_by_key(scope->tags, "clone_only"))); + sentry_value_t scope_tag + = scope_value_get_by_key(sentry__scope_load_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_load_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); @@ -2506,43 +2630,117 @@ 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__attachments_remove(scope->attachments, &attachment_id)); - + sentry_value_decref(sentry__scope_remove_attachment(scope, &attachment_id)); + + sentry_value_t clone_tag + = scope_value_get_by_key(sentry__scope_load_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_load_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->tags, "tag_key")), - "tag_value"); - TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - clone->contexts, "device")), - "Xbox"); - TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - clone->user, "username")), + sentry_value_as_string(sentry_value_get_by_key(clone_user, "username")), "alice"); + sentry_value_decref(clone_user); + sentry_value_t clone_extra + = scope_value_get_by_key(sentry__scope_load_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); + sentry_value_t clone_attribute = scope_value_get_by_key( + sentry__scope_load_attributes, clone, "attr_key"); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - clone->extra, "extra_key")), - "extra_value"); - TEST_CHECK_INT_EQUAL(sentry_value_get_length(clone->fingerprint), 2); - TEST_CHECK(clone->level == 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")), + 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. - 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_load_attachments(clone); + sentry_value_t scope_attachments = sentry__scope_load_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); + sentry_value_decref(scope_attachments); + sentry_value_decref(clone_attachments); sentry_scope_free(clone); 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_load_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_load_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_load_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_load_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); @@ -2559,13 +2757,17 @@ 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_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(clone->transaction_object == 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. @@ -2575,6 +2777,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); @@ -2584,10 +2836,12 @@ 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_value_new_string("keep")); + sentry__scope_set_propagation_context( + 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")); @@ -2604,35 +2858,49 @@ 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(scope_value_get_length(sentry__scope_load_tags, scope) == 1); + TEST_CHECK( + scope_value_get_length(sentry__scope_load_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); 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(scope_value_get_length(sentry__scope_load_tags, scope) == 0); + TEST_CHECK(scope_value_get_length(sentry__scope_load_extra, scope) == 0); + TEST_CHECK(scope_value_get_length(sentry__scope_load_contexts, scope) == 0); + TEST_CHECK( + scope_value_get_length(sentry__scope_load_attributes, scope) == 0); + 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__scope_breadcrumbs_to_list(scope); TEST_CHECK(sentry_value_get_length(crumbs) == 0); sentry_value_decref(crumbs); // ... except the trace, which is preserved. + sentry_value_t propagation_context + = sentry__scope_load_propagation_context(scope); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - scope->propagation_context, "marker")), + propagation_context, "marker")), "keep"); - TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - scope->dynamic_sampling_context, "marker")), + sentry_value_decref(propagation_context); + sentry_value_t scope_dsc = sentry__scope_load_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"); - TEST_CHECK(sentry_value_get_length(scope->tags) == 1); + TEST_CHECK(scope_value_get_length(sentry__scope_load_tags, scope) == 1); sentry_scope_free(scope); sentry_close(); @@ -2825,9 +3093,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(scope->tags, "run")), - "first"); + sentry_value_t tag + = scope_value_get_by_key(sentry__scope_load_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, @@ -2884,8 +3153,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_load_trace_context(global_scope); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(trace, "trace_id")), sentry_value_as_string( @@ -2894,6 +3163,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); @@ -2923,8 +3193,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, @@ -2945,7 +3220,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); @@ -2970,17 +3247,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); @@ -3009,21 +3299,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); @@ -3045,15 +3339,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_sync.c b/tests/unit/test_sync.c index 08f6d6f60c..cfbaa4dca9 100644 --- a/tests/unit/test_sync.c +++ b/tests/unit/test_sync.c @@ -4,6 +4,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/test_tracing.c b/tests/unit/test_tracing.c index ae1cb9588a..cb05c7ccea 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -398,6 +398,16 @@ before_transport(sentry_envelope_t *envelope, void *data) sentry_envelope_free(envelope); } +static sentry_value_t +ref_scope_span_or_transaction(void) +{ + sentry_value_t value = sentry_value_new_null(); + SENTRY_WITH_SCOPE (scope) { + value = sentry__scope_ref_span_or_transaction(scope); + } + return value; +} + SENTRY_TEST(multiple_transactions) { uint64_t called_transport = 0; @@ -420,12 +430,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 = ref_scope_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 = ref_scope_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 +449,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 = ref_scope_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 +533,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 = ref_scope_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 = ref_scope_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 +560,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); @@ -852,7 +869,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(scope->span != 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 +953,19 @@ SENTRY_TEST(discard_transaction) sentry_set_transaction_object(tx); SENTRY_WITH_SCOPE (scope) { - TEST_CHECK(scope->transaction_object == 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(scope->transaction_object == 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 +995,17 @@ SENTRY_TEST(discard_span) sentry_set_span(span); SENTRY_WITH_SCOPE (scope) { - TEST_CHECK(scope->span == 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(scope->span == 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")), @@ -1566,7 +1595,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_load_trace_context(scope); TEST_CHECK(!sentry_value_is_null(propagation_trace_context)); CHECK_STRING_PROPERTY(propagation_trace_context, "type", "trace"); @@ -1579,6 +1608,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,12 +2456,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_value_get_by_key(scope->propagation_context, "trace"), - "trace_id")); + sentry_value_t trace_context = sentry__scope_load_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); @@ -2476,16 +2506,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( - scope->dynamic_sampling_context, "sample_rand")); + sentry_value_t dsc = sentry__scope_load_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( - scope->dynamic_sampling_context, "sample_rand")); + sentry_value_t dsc = sentry__scope_load_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. diff --git a/tests/unit/test_value.c b/tests/unit/test_value.c index 61bb84221b..83a5032816 100644 --- a/tests/unit/test_value.c +++ b/tests/unit/test_value.c @@ -2211,3 +2211,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 f280c56675..09820e8705 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -335,9 +335,17 @@ 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) +XX(scope_attachments) XX(scope_bind_span) XX(scope_bind_span_or_transaction_not_both) XX(scope_bind_transaction_object) @@ -351,6 +359,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) @@ -385,6 +394,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) @@ -527,6 +537,7 @@ XX(value_object_merge_shallow) XX(value_object_merge_shallow_nested) XX(value_refcount) XX(value_remove_by_null_key) +XX(value_replace) XX(value_set_by_null_key) XX(value_set_stacktrace) XX(value_string)