diff --git a/.clang-format b/.clang-format index 84446160f..b6bcfbfd2 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/CHANGELOG.md b/CHANGELOG.md index 72cd88349..5b67be743 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ - Add `sentry_add_attachment`, `sentry_scope_add_attachment`, and `sentry_hint_add_attachment` for adding configured attachments to the global scope, a specific scope, or a hint. ([#1974](https://github.com/getsentry/sentry-native/pull/1974)) - Add `sentry_event_set_level` for setting the level of an individual event. ([#2038](https://github.com/getsentry/sentry-native/pull/2038)) +**Fixes**: + +- Reduce lock contention for multi-threaded log and metric capture by allowing concurrent reads of scope data. ([#2042](https://github.com/getsentry/sentry-native/pull/2042)) + ## 0.16.5 **Important behavior changes**: diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index f5de31a84..c692c3616 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -148,6 +148,7 @@ typedef struct { std::atomic crashed; std::atomic scope_flush; sentry_uuid_t crash_event_id; + sentry_scope_observer_t *scope_observer; } crashpad_state_t; /** @@ -517,7 +518,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( @@ -800,6 +801,91 @@ process_completed_reports( } } +#if defined(SENTRY_PLATFORM_WINDOWS) || defined(SENTRY_PLATFORM_LINUX) \ + || defined(SENTRY_PLATFORM_MACOS) +static sentry_path_t * +make_attachment_path(const sentry_path_t *run_path, sentry_value_t attachment) +{ + if (!sentry__attachment_get_bytes(attachment, nullptr)) { + return sentry__attachment_make_path(attachment); + } + + sentry_uuid_t id = sentry__attachment_get_id(attachment); + const char *filename = sentry__attachment_get_filename(attachment); + if (!run_path || sentry_uuid_is_nil(&id) || !filename) { + return nullptr; + } + + char uuid[37]; + sentry_uuid_as_string(&id, uuid); + sentry_path_t *dir = sentry__path_join_str(run_path, uuid); + sentry_path_t *path = dir ? sentry__path_join_str(dir, filename) : nullptr; + sentry__path_free(dir); + return path; +} + +static void +add_attachment(void *state, sentry_value_t attachment) +{ + auto *data = static_cast(state); + if (!data || !data->client) { + return; + } + + size_t bytes_len = 0; + const char *bytes = sentry__attachment_get_bytes(attachment, &bytes_len); + sentry_path_t *path = make_attachment_path(data->run_path, attachment); + if (!path) { + const char *filename = sentry__attachment_get_filename(attachment); + SENTRY_WARNF("failed to create path for crashpad attachment \"%s\"", + filename ? filename : ""); + return; + } + + if (bytes) { + sentry_path_t *dir = sentry__path_dir(path); + int rv = dir ? sentry__path_create_dir_all(dir) : 1; + sentry__path_free(dir); + if (rv != 0 || sentry__path_write_buffer(path, bytes, bytes_len) != 0) { + SENTRY_WARNF( + "failed to write crashpad attachment \"%s\"", path->path); + sentry__path_remove(path); + sentry__path_free(path); + return; + } + } + data->client->AddAttachment(base::FilePath(SENTRY_PATH_PLATFORM_STR(path))); + sentry__path_free(path); +} + +static void +remove_attachment(void *state, sentry_value_t attachment) +{ + auto *data = static_cast(state); + if (!data || !data->client) { + return; + } + sentry_path_t *path = make_attachment_path(data->run_path, attachment); + if (!path) { + return; + } + data->client->RemoveAttachment( + base::FilePath(SENTRY_PATH_PLATFORM_STR(path))); + + if (sentry__attachment_get_bytes(attachment, nullptr)) { + if (sentry__path_remove(path) != 0) { + SENTRY_WARNF( + "failed to remove crashpad attachment \"%s\"", path->path); + } + if (sentry_path_t *dir = sentry__path_dir(path)) { + sentry__path_remove(dir); + sentry__path_free(dir); + } + } + sentry__path_free(path); +} +#endif + static int crashpad_backend_startup( sentry_backend_t *backend, const sentry_options_t *options) @@ -1028,12 +1114,38 @@ crashpad_backend_startup( crashpad::TriState::kEnabled); } +#if defined(SENTRY_PLATFORM_WINDOWS) || defined(SENTRY_PLATFORM_LINUX) \ + || defined(SENTRY_PLATFORM_MACOS) + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + if (observer) { + observer->data = data; + observer->add_attachment = add_attachment; + observer->remove_attachment = remove_attachment; + SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) { + if (sentry__scope_add_observer(scope, observer)) { + data->scope_observer = observer; + } + } + } +#endif + return 0; } static void crashpad_backend_shutdown(sentry_backend_t *backend) { + auto *data = static_cast(backend->data); +#if defined(SENTRY_PLATFORM_WINDOWS) || defined(SENTRY_PLATFORM_LINUX) \ + || defined(SENTRY_PLATFORM_MACOS) + if (data->scope_observer) { + SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) { + sentry__scope_remove_observer(scope, data->scope_observer); + } + data->scope_observer = nullptr; + } +#endif + #ifdef SENTRY_PLATFORM_LINUX // restore signal handlers to their default state for (const auto signal : g_CrashSignals) { @@ -1043,7 +1155,7 @@ crashpad_backend_shutdown(sentry_backend_t *backend) } #endif - crashpad_state_dtor(static_cast(backend->data)); + crashpad_state_dtor(data); #ifdef SENTRY_PLATFORM_LINUX g_signal_stack.ss_flags = SS_DISABLE; @@ -1214,93 +1326,6 @@ crashpad_backend_prune_database(sentry_backend_t *backend) crashpad::PruneCrashReportDatabase(data->db, &condition); } -#if defined(SENTRY_PLATFORM_WINDOWS) || defined(SENTRY_PLATFORM_LINUX) \ - || defined(SENTRY_PLATFORM_MACOS) -static sentry_path_t * -make_attachment_path(const sentry_path_t *run_path, sentry_value_t attachment) -{ - if (!sentry__attachment_get_bytes(attachment, nullptr)) { - return sentry__attachment_make_path(attachment); - } - - sentry_uuid_t id = sentry__attachment_get_id(attachment); - const char *filename = sentry__attachment_get_filename(attachment); - if (!run_path || sentry_uuid_is_nil(&id) || !filename) { - return nullptr; - } - - char uuid[37]; - sentry_uuid_as_string(&id, uuid); - sentry_path_t *dir = sentry__path_join_str(run_path, uuid); - sentry_path_t *path = dir ? sentry__path_join_str(dir, filename) : nullptr; - sentry__path_free(dir); - return path; -} - -static void -crashpad_backend_add_attachment(sentry_backend_t *backend, - sentry_value_t attachment, const sentry_options_t *UNUSED(options)) -{ - auto *data = static_cast(backend->data); - if (!data || !data->client) { - return; - } - - size_t bytes_len = 0; - const char *bytes = sentry__attachment_get_bytes(attachment, &bytes_len); - sentry_path_t *path = make_attachment_path(data->run_path, attachment); - if (!path) { - const char *filename = sentry__attachment_get_filename(attachment); - SENTRY_WARNF("failed to create path for crashpad attachment \"%s\"", - filename ? filename : ""); - return; - } - - if (bytes) { - sentry_path_t *dir = sentry__path_dir(path); - int rv = dir ? sentry__path_create_dir_all(dir) : 1; - sentry__path_free(dir); - if (rv != 0 || sentry__path_write_buffer(path, bytes, bytes_len) != 0) { - SENTRY_WARNF( - "failed to write crashpad attachment \"%s\"", path->path); - sentry__path_remove(path); - sentry__path_free(path); - return; - } - } - data->client->AddAttachment(base::FilePath(SENTRY_PATH_PLATFORM_STR(path))); - sentry__path_free(path); -} - -static void -crashpad_backend_remove_attachment( - sentry_backend_t *backend, sentry_value_t attachment) -{ - auto *data = static_cast(backend->data); - if (!data || !data->client) { - return; - } - sentry_path_t *path = make_attachment_path(data->run_path, attachment); - if (!path) { - return; - } - data->client->RemoveAttachment( - base::FilePath(SENTRY_PATH_PLATFORM_STR(path))); - - if (sentry__attachment_get_bytes(attachment, nullptr)) { - if (sentry__path_remove(path) != 0) { - SENTRY_WARNF( - "failed to remove crashpad attachment \"%s\"", path->path); - } - if (sentry_path_t *dir = sentry__path_dir(path)) { - sentry__path_remove(dir); - sentry__path_free(dir); - } - } - sentry__path_free(path); -} -#endif - void sentry__backend_preload(void) { @@ -1332,11 +1357,6 @@ sentry__backend_new(void) backend->get_last_crash_func = crashpad_backend_last_crash; backend->process_old_run_func = crashpad_backend_process_old_run; 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; -#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 5ca610703..b1fe54870 100644 --- a/src/backends/sentry_backend_native.c +++ b/src/backends/sentry_backend_native.c @@ -180,14 +180,78 @@ 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; sentry_path_t *envelope_path; size_t num_breadcrumbs; volatile long crashed; + sentry_scope_observer_t *scope_observer; } native_backend_state_t; +/** + * Creates an attachment path, deriving a unique path in the run directory for + * buffer attachments. + */ +static sentry_path_t * +make_attachment_path(const sentry_path_t *run_path, sentry_value_t attachment) +{ + if (!sentry__attachment_get_bytes(attachment, NULL)) { + return sentry__attachment_make_path(attachment); + } + + sentry_uuid_t id = sentry__attachment_get_id(attachment); + const char *filename = sentry__attachment_get_filename(attachment); + if (!run_path || sentry_uuid_is_nil(&id) || !filename) { + return NULL; + } + + char uuid[37]; + sentry_uuid_as_string(&id, uuid); + sentry_path_t *dir = sentry__path_join_str(run_path, uuid); + sentry_path_t *path = dir ? sentry__path_join_str(dir, filename) : NULL; + sentry__path_free(dir); + return path; +} + +static void +add_attachment(void *data, sentry_value_t attachment) +{ + 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(state->run_path, attachment); + if (!path) { + const char *filename = sentry__attachment_get_filename(attachment); + SENTRY_WARNF("failed to create path for native backend attachment " + "\"%s\"", + filename ? filename : ""); + return; + } + sentry_path_t *dir = sentry__path_dir(path); + int rv = dir ? sentry__path_create_dir_all(dir) : 1; + sentry__path_free(dir); + // Write buffer to disk + if (rv != 0 || sentry__path_write_buffer(path, bytes, bytes_len) != 0) { + SENTRY_WARNF( + "failed to write native backend attachment \"%s\"", path->path); + sentry__path_remove(path); + } + sentry__path_free(path); + } + // For file attachments, the path is already set and points to the actual + // file. The crash daemon will read these files from their original + // locations. +} + static bool native_backend_process_old_run(sentry_backend_t *backend, const sentry_options_t *options, const sentry_path_t *run_path) @@ -568,7 +632,7 @@ native_backend_startup( return 1; } backend->data = state; - + state->run_path = sentry__path_clone(options->run->run_path); // Initialize IPC (protected by global synchronization for concurrent // access) #if defined(SENTRY_PLATFORM_WINDOWS) @@ -582,6 +646,7 @@ native_backend_startup( #endif if (!state->ipc) { SENTRY_WARN("failed to initialize crash IPC"); + sentry__path_free(state->run_path); sentry_free(state); backend->data = NULL; return 1; @@ -596,6 +661,7 @@ native_backend_startup( SENTRY_WARNF("failed to acquire mutex for context setup: %lu", GetLastError()); sentry__crash_ipc_free(state->ipc); + sentry__path_free(state->run_path); sentry_free(state); backend->data = NULL; return 1; @@ -608,6 +674,7 @@ native_backend_startup( SENTRY_WARNF("failed to acquire semaphore for context setup: %s", strerror(errno)); sentry__crash_ipc_free(state->ipc); + sentry__path_free(state->run_path); sentry_free(state); backend->data = NULL; return 1; @@ -792,6 +859,7 @@ native_backend_startup( < 0) { SENTRY_WARN("failed to initialize crash handler"); sentry__crash_ipc_free(state->ipc); + sentry__path_free(state->run_path); sentry_free(state); backend->data = NULL; return 1; @@ -825,6 +893,7 @@ native_backend_startup( # endif SENTRY_WARN("failed to start crash daemon"); sentry__crash_ipc_free(state->ipc); + sentry__path_free(state->run_path); sentry_free(state); backend->data = NULL; return 1; @@ -894,6 +963,7 @@ native_backend_startup( } # endif sentry__crash_ipc_free(state->ipc); + sentry__path_free(state->run_path); sentry_free(state); backend->data = NULL; return 1; @@ -901,6 +971,16 @@ native_backend_startup( #endif SENTRY_DEBUG("native backend started successfully"); + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + if (observer) { + observer->data = state; + observer->add_attachment = add_attachment; + SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) { + if (sentry__scope_add_observer(scope, observer)) { + state->scope_observer = observer; + } + } + } return 0; } @@ -914,6 +994,13 @@ native_backend_shutdown(sentry_backend_t *backend) return; } + if (state->scope_observer) { + SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) { + sentry__scope_remove_observer(scope, state->scope_observer); + } + state->scope_observer = NULL; + } + #if defined(SENTRY_PLATFORM_WINDOWS) && !defined(SENTRY_PLATFORM_XBOX) wer_unregister_module(); #endif @@ -1040,35 +1127,11 @@ 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); } -/** - * Creates an attachment path, deriving a unique path in the run directory for - * buffer attachments. - */ -static sentry_path_t * -make_attachment_path(const sentry_path_t *run_path, sentry_value_t attachment) -{ - if (!sentry__attachment_get_bytes(attachment, NULL)) { - return sentry__attachment_make_path(attachment); - } - - sentry_uuid_t id = sentry__attachment_get_id(attachment); - const char *filename = sentry__attachment_get_filename(attachment); - if (!run_path || sentry_uuid_is_nil(&id) || !filename) { - return NULL; - } - - char uuid[37]; - sentry_uuid_as_string(&id, uuid); - sentry_path_t *dir = sentry__path_join_str(run_path, uuid); - sentry_path_t *path = dir ? sentry__path_join_str(dir, filename) : NULL; - sentry__path_free(dir); - return path; -} - // Writes the scope's attachment list to /__sentry-attachments so the // crash daemon can locate and append them to the crash envelope. static void @@ -1078,12 +1141,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 @@ -1139,6 +1204,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); } } @@ -1262,42 +1328,6 @@ 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) -{ - (void)backend; // Unused - - // 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); - if (!path) { - const char *filename = sentry__attachment_get_filename(attachment); - SENTRY_WARNF("failed to create path for native backend attachment " - "\"%s\"", - filename ? filename : ""); - return; - } - sentry_path_t *dir = sentry__path_dir(path); - int rv = dir ? sentry__path_create_dir_all(dir) : 1; - sentry__path_free(dir); - // Write buffer to disk - if (rv != 0 || sentry__path_write_buffer(path, bytes, bytes_len) != 0) { - SENTRY_WARNF( - "failed to write native backend attachment \"%s\"", path->path); - sentry__path_remove(path); - } - sentry__path_free(path); - } - // For file attachments, the path is already set and points to the actual - // file. The crash daemon will read these files from their original - // locations. -} - /** * Handle exception - called from signal handler via sentry_handle_exception * This processes the event with on_crash/before_send hooks and ends the session @@ -1470,7 +1500,6 @@ 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->user_consent_changed_func = native_backend_user_consent_changed; backend->process_old_run_func = native_backend_process_old_run; backend->can_capture_after_shutdown = false; diff --git a/src/integrations/sentry_integration_wer.c b/src/integrations/sentry_integration_wer.c index 23f9af316..33d342306 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 int wer_cleanup_tag(const char *key, sentry_value_t UNUSED(value), void *data) { @@ -208,13 +220,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 @@ -239,11 +249,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); } } @@ -257,13 +263,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.h b/src/sentry_backend.h index 7eec78d3d..c1e54288a 100644 --- a/src/sentry_backend.h +++ b/src/sentry_backend.h @@ -29,9 +29,6 @@ struct sentry_backend_s { bool (*process_old_run_func)(sentry_backend_t *, const sentry_options_t *options, const sentry_path_t *run_path); 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); 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 0b76c0eb7..092ccb2a9 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -80,23 +80,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) { @@ -278,38 +261,7 @@ sentry_init(sentry_options_t *options) // `client_sdk` in the `scope` because some downstream SDKs want to override // it at runtime via the options interface. SENTRY_WITH_SCOPE_MUT (scope) { - if (options->sdk_name) { - sentry_value_t sdk_name - = sentry_value_new_string(options->sdk_name); - sentry_value_set_by_key(scope->client_sdk, "name", sdk_name); - } - sentry_value_t integrations - = sentry_value_get_by_key(scope->client_sdk, "integrations"); - for (size_t i = 0; i < options->num_integrations; i++) { - const char *name = options->integrations[i]->name; - if (!name) { - continue; - } - if (sentry_value_is_null(integrations)) { - integrations = sentry_value_new_list(); - sentry_value_set_by_key( - scope->client_sdk, "integrations", integrations); - } - sentry_value_append(integrations, sentry_value_new_string(name)); - } - sentry_value_freeze(scope->client_sdk); - generate_propagation_context(scope->propagation_context); - scope->release = sentry__string_clone(options->release); - scope->environment = sentry__string_clone(options->environment); - sentry_value_decref(scope->attachments); - scope->attachments = options->attachments; - options->attachments = sentry_value_new_null(); - - sentry__ringbuffer_set_max_size( - scope->breadcrumbs, options->max_breadcrumbs); - - sentry__scope_update_dsc(scope, options); - + sentry__scope_apply_options(scope, options); register_integrations(scope, options); } if (backend && backend->user_consent_changed_func) { @@ -554,7 +506,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); } } @@ -789,14 +741,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); @@ -804,6 +762,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); @@ -928,13 +887,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); @@ -942,6 +906,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); @@ -1282,7 +1247,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); } } @@ -1384,7 +1349,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)) { @@ -1405,8 +1370,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); } } @@ -1485,12 +1450,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"); @@ -1503,9 +1468,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 }; @@ -1551,10 +1517,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); @@ -1572,28 +1535,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 @@ -1653,7 +1603,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); } } @@ -1661,7 +1611,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); } } @@ -1813,18 +1763,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 @@ -1883,10 +1822,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); @@ -2158,16 +2094,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); @@ -2207,17 +2134,11 @@ sentry_clear_attachments(void) { SENTRY_WITH_OPTIONS (options) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_t attachments = scope->attachments; - scope->attachments = sentry_value_new_list(); + 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); @@ -2232,20 +2153,8 @@ sentry_remove_attachment(sentry_uuid_t attachment_id) return; } - SENTRY_WITH_OPTIONS (options) { - SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_t removed = sentry__attachments_remove( - scope->attachments, &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); - } + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_scope_remove_attachment(scope, attachment_id); } } diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index ffefecbd8..4b72ef653 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 18445a220..9f36fe0fc 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,233 @@ 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_value_new_list(); - 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_value_new_list(); + 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()); +#if defined(SENTRY_PLATFORM_WINDOWS) && !defined(SENTRY_PLATFORM_XBOX) + sentry_value_t wine_context = sentry__get_wine_context(); + if (!sentry_value_is_null(wine_context)) { + sentry_value_set_by_key(data->contexts, "wine", wine_context); + } else { + sentry_value_decref(wine_context); + } +#endif + 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_value_new_list(); + 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,18 +369,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()); -#if defined(SENTRY_PLATFORM_WINDOWS) && !defined(SENTRY_PLATFORM_XBOX) - sentry_value_t wine_context = sentry__get_wine_context(); - if (!sentry_value_is_null(wine_context)) { - sentry_value_set_by_key(g_scope.contexts, "wine", wine_context); - } else { - sentry_value_decref(wine_context); + 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; } -#endif - g_scope.client_sdk = get_client_sdk(); + init_global_data(g_scope.data); g_scope_initialized = true; @@ -125,71 +383,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) { @@ -199,18 +505,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) { @@ -225,10 +519,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; } @@ -240,6 +536,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; } @@ -247,7 +544,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; } @@ -260,6 +563,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++) { @@ -270,13 +574,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; } @@ -285,9 +592,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; } @@ -304,6 +613,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 * @@ -314,25 +624,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); } } @@ -342,11 +665,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) { @@ -363,35 +725,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 * @@ -406,79 +740,116 @@ 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_value_new_list(); - 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; + } +} - scope->dynamic_sampling_context = dsc; +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, + sentry_value_foreach_key_value_function_t callback, void *userdata) +{ + SENTRY_SCOPE_READ_LOCK (scope->data) { + sentry_value_foreach_key_value( + scope->data->dynamic_sampling_context, callback, userdata); + } +} + +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) @@ -584,27 +955,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 @@ -621,6 +979,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)) { \ @@ -637,55 +1001,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 fingerprint = sentry__scope_ref_fingerprint(scope); + PLACE_CLONED_VALUE("fingerprint", fingerprint); + sentry_value_decref(fingerprint); - 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); + 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); } - } else { - sentry__value_merge_objects(event_tags, scope->tags); - } - sentry_value_t event_extra = sentry_value_get_by_key(event, "extra"); - if (sentry_value_is_null(event_extra)) { - if (!sentry_value_is_null(scope->extra)) { - PLACE_CLONED_VALUE("extra", scope->extra); + + sentry_value_t event_extra = sentry_value_get_by_key(event, "extra"); + if (sentry_value_is_null(event_extra)) { + if (!sentry_value_is_null(scope->data->extra)) { + 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"); } @@ -695,7 +1085,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)) { @@ -711,6 +1101,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"); @@ -718,7 +1109,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); @@ -731,7 +1125,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)); @@ -755,6 +1149,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 @@ -763,25 +1158,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); } } @@ -790,11 +1228,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 int @@ -819,7 +1272,11 @@ sentry_scope_set_tags(sentry_scope_t *scope, sentry_value_t tags) void sentry_scope_remove_tag(sentry_scope_t *scope, const char *key) { - if (sentry_value_remove_by_key(scope->tags, key) == 0) { + 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); } } @@ -828,36 +1285,75 @@ void sentry_scope_remove_tag_n( sentry_scope_t *scope, const char *key, size_t key_len) { - char *k = sentry__value_remove_and_take_key_n(scope->tags, key, key_len); - if (k) { - SENTRY_SCOPE_NOTIFY(scope, remove_tag, k); + 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); } - sentry_free(k); + 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) { - if (sentry_value_remove_by_key(scope->extra, key) == 0) { + 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); } } @@ -866,11 +1362,15 @@ void sentry_scope_remove_extra_n( sentry_scope_t *scope, const char *key, size_t key_len) { - char *k = sentry__value_remove_and_take_key_n(scope->extra, key, key_len); - if (k) { - SENTRY_SCOPE_NOTIFY(scope, remove_extra, k); + 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); } - sentry_free(k); + if (removed_key) { + SENTRY_SCOPE_NOTIFY(scope, remove_extra, removed_key); + } + sentry_free(removed_key); } void @@ -891,46 +1391,98 @@ 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 sentry_scope_remove_context(sentry_scope_t *scope, const char *key) { - if (sentry_value_remove_by_key(scope->contexts, key) == 0) { + 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); } } @@ -939,12 +1491,15 @@ void sentry_scope_remove_context_n( sentry_scope_t *scope, const char *key, size_t key_len) { - char *k - = sentry__value_remove_and_take_key_n(scope->contexts, key, key_len); - if (k) { - SENTRY_SCOPE_NOTIFY(scope, remove_context, k); + 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); } - sentry_free(k); + if (removed_key) { + SENTRY_SCOPE_NOTIFY(scope, remove_context, removed_key); + } + sentry_free(removed_key); } void @@ -959,33 +1514,43 @@ 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_SCOPE_NOTIFY(scope, set_context, k, value); + sentry_free(notify_key); + sentry_value_decref(value); } void sentry_scope_set_release_n( sentry_scope_t *scope, const char *release, size_t release_len) { - sentry_free(scope->release); - scope->release = sentry__string_clone_n(release, release_len); - sentry_value_set_by_key(scope->dynamic_sampling_context, "release", - sentry_value_new_string(scope->release)); - SENTRY_SCOPE_NOTIFY(scope, set_release, scope->release); + sentry_value_t value = sentry_value_new_string_n(release, release_len); + 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); } void @@ -998,11 +1563,15 @@ void sentry_scope_set_environment_n( sentry_scope_t *scope, const char *environment, size_t environment_len) { - sentry_free(scope->environment); - scope->environment = sentry__string_clone_n(environment, environment_len); - sentry_value_set_by_key(scope->dynamic_sampling_context, "environment", - sentry_value_new_string(scope->environment)); - SENTRY_SCOPE_NOTIFY(scope, set_environment, scope->environment); + sentry_value_t value + = sentry_value_new_string_n(environment, environment_len); + 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); } void @@ -1016,14 +1585,17 @@ void sentry_scope_set_transaction_n( sentry_scope_t *scope, const char *transaction, size_t transaction_len) { - sentry_free(scope->transaction); - scope->transaction = sentry__string_clone_n(transaction, transaction_len); - - if (scope->transaction_object) { - sentry_transaction_set_name_n( - scope->transaction_object, transaction, transaction_len); + sentry_value_t value + = sentry_value_new_string_n(transaction, transaction_len); + 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(scope, set_transaction, scope->transaction); + SENTRY_SCOPE_NOTIFY_OWNED(scope, set_transaction, value); } void @@ -1033,6 +1605,16 @@ sentry_scope_set_transaction(sentry_scope_t *scope, const char *transaction) scope, transaction, sentry__guarded_strlen(transaction)); } +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 sentry__scope_set_fingerprint_va( sentry_scope_t *scope, const char *fingerprint, va_list va) @@ -1043,9 +1625,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 @@ -1098,23 +1678,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); } @@ -1122,38 +1729,53 @@ 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_value_new_list(); + } + return attachments; +} + void sentry_scope_remove_attachment( sentry_scope_t *scope, sentry_uuid_t attachment_id) @@ -1162,8 +1784,11 @@ sentry_scope_remove_attachment( return; } - sentry_value_t removed - = sentry__attachments_remove(scope->attachments, &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); + } if (!sentry_value_is_null(removed)) { SENTRY_SCOPE_NOTIFY(scope, remove_attachment, removed); } @@ -1184,6 +1809,194 @@ 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; +} + +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; +} + +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; +} + sentry_uuid_t sentry_scope_attach_file(sentry_scope_t *scope, const char *path) { @@ -1253,47 +2066,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( @@ -1301,53 +2144,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 @@ -1357,7 +2204,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 aa758a824..b28b3643c 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,87 @@ 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); + +sentry_value_t sentry__scope_ref_environment(const sentry_scope_t *scope); + +sentry_value_t sentry__scope_ref_transaction(const sentry_scope_t *scope); + +sentry_value_t sentry__scope_ref_fingerprint(const sentry_scope_t *scope); void sentry__scope_set_fingerprint_va( sentry_scope_t *scope, const char *fingerprint, va_list va); void sentry__scope_set_fingerprint_nva(sentry_scope_t *scope, const char *fingerprint, size_t fingerprint_len, va_list va); +sentry_value_t sentry__scope_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_load_tags(const sentry_scope_t *scope); + +sentry_value_t sentry__scope_load_extra(const sentry_scope_t *scope); + +sentry_value_t sentry__scope_load_attributes(const sentry_scope_t *scope); + +sentry_value_t sentry__scope_load_contexts(const sentry_scope_t *scope); + +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 +230,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 +239,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 +261,10 @@ 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, + sentry_value_foreach_key_value_function_t callback, 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 +288,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 +302,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 182dfa92c..267365f94 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_tracing.c b/src/sentry_tracing.c index f6dd8cd0f..3ab00feff 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -97,23 +97,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; @@ -965,8 +962,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); @@ -1020,14 +1016,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 @@ -1042,12 +1032,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 5ba423d17..2c9907794 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 f953741be..c5aba36fc 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 5cce77441..be7931124 100644 --- a/src/sentry_value.h +++ b/src/sentry_value.h @@ -79,6 +79,12 @@ sentry_value_t sentry__value_new_object_with_size(size_t size); 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 ba9da7055..e622fa99e 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_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) @@ -144,11 +144,9 @@ SENTRY_TEST(lazy_attachments) SENTRY_TEST(attachments_add_dedupe) { SENTRY_TEST_OPTIONS_NEW(options); - size_t backend_add_count = 0; + size_t 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; 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"); @@ -158,6 +156,14 @@ SENTRY_TEST(attachments_add_dedupe) sentry_init(options); + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + TEST_ASSERT(!!observer); + observer->data = &add_count; + observer->add_attachment = count_attachment; + SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) { + TEST_ASSERT(sentry__scope_add_observer(scope, observer)); + } + sentry_attach_file(SENTRY_TEST_PATH_PREFIX ".a.txt"); sentry_attach_file(SENTRY_TEST_PATH_PREFIX ".b.txt"); sentry_attach_file(SENTRY_TEST_PATH_PREFIX ".c.txt"); @@ -166,7 +172,7 @@ SENTRY_TEST(attachments_add_dedupe) sentry_attach_filew(SENTRY_TEST_PATH_PREFIX L".b.txt"); sentry_attach_filew(SENTRY_TEST_PATH_PREFIX L".c.txt"); #endif - TEST_CHECK_INT_EQUAL(backend_add_count, 1); + TEST_CHECK_INT_EQUAL(add_count, 1); sentry_path_t *path_a = sentry__path_from_str(SENTRY_TEST_PATH_PREFIX ".a.txt"); @@ -193,6 +199,9 @@ SENTRY_TEST(attachments_add_dedupe) sentry_free(serialized); + SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) { + sentry__scope_remove_observer(scope, observer); + } sentry_close(); sentry__path_remove(path_a); @@ -210,9 +219,13 @@ SENTRY_TEST(attachments_add_remove) sentry_uuid_t scoped_attachment = sentry_scope_attach_bytes(scope, "payload", 7, "file.bin"); TEST_CHECK(!sentry_uuid_is_nil(&scoped_attachment)); - TEST_CHECK_INT_EQUAL(sentry_value_get_length(scope->attachments), 1); + sentry_value_t scoped_attachments = sentry__scope_load_attachments(scope); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(scoped_attachments), 1); + sentry_value_decref(scoped_attachments); sentry_scope_remove_attachment(scope, scoped_attachment); - TEST_CHECK_INT_EQUAL(sentry_value_get_length(scope->attachments), 0); + scoped_attachments = sentry__scope_load_attachments(scope); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(scoped_attachments), 0); + sentry_value_decref(scoped_attachments); sentry_scope_remove_attachment(scope, sentry_uuid_nil()); sentry_scope_free(scope); @@ -398,7 +411,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); } sentry_value_t invalid = sentry_attachment_from_file(NULL); diff --git a/tests/unit/test_basic.c b/tests/unit/test_basic.c index 0c2fd735b..6a104baf9 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 cdc3c739a..e0bdd4133 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 24710b63c..b75f72930 100644 --- a/tests/unit/test_logs.c +++ b/tests/unit/test_logs.c @@ -732,12 +732,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 d50354556..2c1ab08e6 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); @@ -73,10 +95,14 @@ SENTRY_TEST(scope_contexts) local_scope, "n-removed", sentry_value_new_string("removed")); sentry_scope_remove_context(local_scope, "removed"); sentry_scope_remove_context_n(local_scope, "n-removed-trailing", 9); - TEST_CHECK(sentry_value_is_null( - sentry_value_get_by_key(local_scope->contexts, "removed"))); - TEST_CHECK(sentry_value_is_null( - sentry_value_get_by_key(local_scope->contexts, "n-removed"))); + sentry_value_t removed = scope_value_get_by_key( + sentry__scope_load_contexts, local_scope, "removed"); + TEST_CHECK(sentry_value_is_null(removed)); + sentry_value_decref(removed); + removed = scope_value_get_by_key( + sentry__scope_load_contexts, local_scope, "n-removed"); + TEST_CHECK(sentry_value_is_null(removed)); + sentry_value_decref(removed); // event: // {"all":"event","event":"event"} @@ -133,14 +159,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); } } @@ -153,8 +180,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"); @@ -165,6 +192,7 @@ SENTRY_TEST(scope_update_context) sentry_value_as_string( sentry_value_get_by_key(ctx, "cpu_description")), "some cpu"); + sentry_value_decref(ctx); } } @@ -176,11 +204,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(); @@ -188,13 +217,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); } @@ -343,10 +374,14 @@ SENTRY_TEST(scope_extra) local_scope, "n-removed", sentry_value_new_string("removed")); sentry_scope_remove_extra(local_scope, "removed"); sentry_scope_remove_extra_n(local_scope, "n-removed-trailing", 9); - TEST_CHECK(sentry_value_is_null( - sentry_value_get_by_key(local_scope->extra, "removed"))); - TEST_CHECK(sentry_value_is_null( - sentry_value_get_by_key(local_scope->extra, "n-removed"))); + sentry_value_t removed = scope_value_get_by_key( + sentry__scope_load_extra, local_scope, "removed"); + TEST_CHECK(sentry_value_is_null(removed)); + sentry_value_decref(removed); + removed = scope_value_get_by_key( + sentry__scope_load_extra, local_scope, "n-removed"); + TEST_CHECK(sentry_value_is_null(removed)); + sentry_value_decref(removed); // event: // {"all":"event","event":"event"} @@ -463,6 +498,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); } @@ -597,10 +642,14 @@ SENTRY_TEST(scope_tags) sentry_scope_set_tag(local_scope, "n-removed", "removed"); sentry_scope_remove_tag(local_scope, "removed"); sentry_scope_remove_tag_n(local_scope, "n-removed-trailing", 9); - TEST_CHECK(sentry_value_is_null( - sentry_value_get_by_key(local_scope->tags, "removed"))); - TEST_CHECK(sentry_value_is_null( - sentry_value_get_by_key(local_scope->tags, "n-removed"))); + sentry_value_t removed = scope_value_get_by_key( + sentry__scope_load_tags, local_scope, "removed"); + TEST_CHECK(sentry_value_is_null(removed)); + sentry_value_decref(removed); + removed = scope_value_get_by_key( + sentry__scope_load_tags, local_scope, "n-removed"); + TEST_CHECK(sentry_value_is_null(removed)); + sentry_value_decref(removed); // event: // {"all":"event","event":"event"} @@ -688,6 +737,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); } @@ -1123,28 +1179,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(); @@ -1161,7 +1223,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"); @@ -1173,6 +1235,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 @@ -1183,12 +1246,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' @@ -1199,24 +1263,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 @@ -1225,7 +1291,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"); @@ -1240,6 +1306,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(); @@ -1260,7 +1327,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( @@ -1275,6 +1342,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) { @@ -1288,7 +1356,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( @@ -1305,7 +1374,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")), @@ -1314,6 +1384,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); } @@ -1322,7 +1394,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 @@ -1330,6 +1402,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 @@ -1338,7 +1411,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"); @@ -1352,10 +1426,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); } @@ -1370,9 +1448,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); } @@ -1387,11 +1467,14 @@ SENTRY_TEST(scope_release) SENTRY_WITH_SCOPE_MUT (scope) { sentry_scope_set_release(scope, "my-release"); - TEST_CHECK_STRING_EQUAL(scope->release, "my-release"); + sentry_value_t release = sentry__scope_ref_release(scope); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(release), "my-release"); + sentry_value_decref(release); + sentry_value_t dsc = sentry__scope_load_dsc(scope); TEST_CHECK_STRING_EQUAL( - sentry_value_as_string(sentry_value_get_by_key( - scope->dynamic_sampling_context, "release")), + sentry_value_as_string(sentry_value_get_by_key(dsc, "release")), "my-release"); + sentry_value_decref(dsc); } sentry_close(); @@ -1404,11 +1487,15 @@ SENTRY_TEST(scope_environment) SENTRY_WITH_SCOPE_MUT (scope) { sentry_scope_set_environment(scope, "my-environment"); - TEST_CHECK_STRING_EQUAL(scope->environment, "my-environment"); + sentry_value_t environment = sentry__scope_ref_environment(scope); TEST_CHECK_STRING_EQUAL( - sentry_value_as_string(sentry_value_get_by_key( - scope->dynamic_sampling_context, "environment")), + sentry_value_as_string(environment), "my-environment"); + sentry_value_decref(environment); + sentry_value_t dsc = sentry__scope_load_dsc(scope); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(dsc, "environment")), "my-environment"); + sentry_value_decref(dsc); } sentry_close(); @@ -1421,7 +1508,10 @@ SENTRY_TEST(scope_transaction) SENTRY_WITH_SCOPE_MUT (scope) { sentry_scope_set_transaction(scope, "my-transaction"); - TEST_CHECK_STRING_EQUAL(scope->transaction, "my-transaction"); + sentry_value_t transaction = sentry__scope_ref_transaction(scope); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(transaction), "my-transaction"); + sentry_value_decref(transaction); } sentry_close(); @@ -1442,6 +1532,7 @@ typedef struct { bool was_called; bool was_cleared; size_t set_tag_count; + sentry_scope_t *scope; } test_observer_data_t; typedef struct { @@ -1472,10 +1563,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; } @@ -1494,18 +1586,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; } @@ -1574,6 +1668,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; } @@ -1582,6 +1680,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(); } @@ -1721,8 +1825,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"); @@ -1748,9 +1852,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); @@ -1978,6 +2081,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(); } @@ -2000,6 +2112,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(); } @@ -2023,6 +2144,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(); } @@ -2120,6 +2252,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() }; @@ -2128,28 +2261,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); @@ -2478,7 +2616,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); } @@ -2495,7 +2634,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); } @@ -2505,18 +2645,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; @@ -2535,13 +2682,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. @@ -2550,11 +2698,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); @@ -2586,43 +2737,115 @@ 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_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_scope_remove_attachment(scope, first_id); + + 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); @@ -2639,13 +2862,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. @@ -2655,6 +2882,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); @@ -2664,10 +2941,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")); @@ -2684,35 +2963,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(); @@ -2905,9 +3198,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, @@ -2964,8 +3258,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( @@ -2974,6 +3268,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); @@ -3003,8 +3298,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, @@ -3025,7 +3325,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); @@ -3050,17 +3352,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); @@ -3089,21 +3404,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); @@ -3125,15 +3444,19 @@ SENTRY_TEST(scope_clone_keeps_bound_span) sentry_scope_t *scope = sentry_scope_new(); sentry_scope_set_span(scope, span); sentry_scope_t *clone = sentry_scope_clone(scope); - TEST_CHECK_PTR_EQUAL(clone->span, span); + sentry_span_t *clone_span = sentry__scope_ref_span(clone); + TEST_CHECK_PTR_EQUAL(clone_span, span); + sentry__span_decref(clone_span); // The clone owns its binding, so it outlives the original scope and caller // reference. sentry_scope_free(scope); sentry_span_finish(span); + clone_span = sentry__scope_ref_span(clone); TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( - clone->span->inner, "description")), + clone_span->inner, "description")), "select"); + sentry__span_decref(clone_span); sentry_scope_free(clone); sentry_transaction_finish(tx); diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index ae1cb9588..cb05c7cce 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 228883c12..3923fa9af 100644 --- a/tests/unit/test_value.c +++ b/tests/unit/test_value.c @@ -2313,3 +2313,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 b541a7bc5..bc6c432d4 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -355,6 +355,7 @@ XX(rwlock_write_blocks_writer) XX(sampling_before_send) XX(sampling_decision) XX(sampling_transaction) +XX(scope_attachments) XX(scope_bind_span) XX(scope_bind_span_or_transaction_not_both) XX(scope_bind_transaction_object) @@ -368,6 +369,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) @@ -404,6 +406,7 @@ XX(scope_propagation_context) XX(scope_rebind_same_object) XX(scope_release) 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) @@ -548,6 +551,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)