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 692fc5ee8..d2ea72e72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,10 @@ - Deprecate `sentry_clear_crashed_last_run()` because `sentry_init()` now consumes the marker automatically. ([#2023](https://github.com/getsentry/sentry-native/pull/2023)) +**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.6 **Features**: diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index eecf80e1a..de6ea1090 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; /** @@ -800,6 +801,99 @@ 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) + || sentry__string_empty(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_t *parent = path ? sentry__path_dir(path) : nullptr; + bool valid = parent && sentry__path_eq(parent, dir); + sentry__path_free(parent); + sentry__path_free(dir); + if (!valid) { + sentry__path_free(path); + return nullptr; + } + 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 +1122,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 +1163,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,101 +1334,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) - || sentry__string_empty(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_t *parent = path ? sentry__path_dir(path) : nullptr; - bool valid = parent && sentry__path_eq(parent, dir); - sentry__path_free(parent); - sentry__path_free(dir); - if (!valid) { - sentry__path_free(path); - return nullptr; - } - 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) { @@ -1340,11 +1365,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 6906af85c..f06dd6464 100644 --- a/src/backends/sentry_backend_native.c +++ b/src/backends/sentry_backend_native.c @@ -205,14 +205,86 @@ wer_register_module(uint64_t app_tid, const sentry_options_t *options) 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) + || sentry__string_empty(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_t *parent = path ? sentry__path_dir(path) : NULL; + bool valid = parent && sentry__path_eq(parent, dir); + sentry__path_free(parent); + sentry__path_free(dir); + if (!valid) { + sentry__path_free(path); + return NULL; + } + 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) @@ -593,7 +665,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) @@ -607,6 +679,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; @@ -621,6 +694,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; @@ -633,6 +707,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; @@ -817,6 +892,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; @@ -850,6 +926,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; @@ -919,6 +996,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; @@ -926,6 +1004,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; } @@ -939,6 +1027,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 @@ -1065,43 +1160,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) - || sentry__string_empty(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_t *parent = path ? sentry__path_dir(path) : NULL; - bool valid = parent && sentry__path_eq(parent, dir); - sentry__path_free(parent); - sentry__path_free(dir); - if (!valid) { - sentry__path_free(path); - return NULL; - } - 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 @@ -1298,42 +1361,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 @@ -1506,7 +1533,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/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 e93b269eb..06ffb0bb0 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -393,7 +393,8 @@ sentry_reinstall_backend(void) int rv = 0; SENTRY_WITH_OPTIONS (options) { // prevent scope observers from racing with backend reinstall - (void)sentry__scope_lock(); + sentry_scope_t *scope = sentry__scope_getref(); + sentry__mutex_lock(&scope->observers_lock); sentry_backend_t *backend = options->backend; if (backend && backend->shutdown_func) { backend->shutdown_func(backend); @@ -404,7 +405,8 @@ sentry_reinstall_backend(void) rv = 1; } } - sentry__scope_unlock(); + sentry__mutex_unlock(&scope->observers_lock); + sentry__scope_finish(scope, false); } return rv; } @@ -2101,18 +2103,7 @@ sentry_add_attachment(sentry_value_t attachment) sentry_value_t added = sentry_value_new_null(); SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_t attachments = sentry__scope_load_attachments(scope); - added = sentry__attachments_find(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); - } - sentry_value_decref(attachments); + added = sentry__scope_add_attachment(scope, attachment); } sentry_options_free((sentry_options_t *)options); sentry_uuid_t uuid = sentry__attachment_get_id(added); @@ -2157,11 +2148,6 @@ sentry_clear_attachments(void) 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); @@ -2176,22 +2162,8 @@ sentry_remove_attachment(sentry_uuid_t attachment_id) return; } - SENTRY_WITH_OPTIONS (options) { - SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_t attachments = sentry__scope_load_attachments(scope); - sentry_value_t removed - = sentry__attachments_remove(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_value_decref(attachments); - } + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_scope_remove_attachment(scope, attachment_id); } } diff --git a/src/sentry_scope.c b/src/sentry_scope.c index a22065be9..aca024c1a 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -29,9 +29,11 @@ #endif struct sentry_scope_data_s { - char *release; - char *environment; - char *transaction; + 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; @@ -62,12 +64,39 @@ struct sentry_scope_data_s { 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) { @@ -105,9 +134,9 @@ get_client_sdk(void) static void init_scope_data(sentry_scope_data_t *data) { - data->release = NULL; - data->environment = NULL; - data->transaction = NULL; + 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(); @@ -129,9 +158,9 @@ init_scope_data(sentry_scope_data_t *data) static void cleanup_scope_data(sentry_scope_data_t *data) { - sentry_free(data->release); - sentry_free(data->environment); - sentry_free(data->transaction); + 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); @@ -152,6 +181,7 @@ 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; @@ -164,28 +194,52 @@ free_scope_data(sentry_scope_data_t *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) { - // Keep the propagation and dynamic sampling contexts across clears so - // telemetry captured afterwards continues on the same trace. - bool trace_managed = data->trace_managed; - sentry_value_t propagation_context = data->propagation_context; - sentry_value_t dynamic_sampling_context = data->dynamic_sampling_context; - sentry_value_incref(propagation_context); - sentry_value_incref(dynamic_sampling_context); + 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); + 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; + 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 * @@ -196,33 +250,35 @@ clone_scope_data(const sentry_scope_data_t *source) return NULL; } - clone->release = sentry__string_clone(source->release); - clone->environment = sentry__string_clone(source->environment); - clone->transaction = sentry__string_clone(source->transaction); - clone->fingerprint = sentry__value_clone(source->fingerprint); - clone->user = sentry__value_clone(source->user); - clone->tags = sentry__value_clone(source->tags); - clone->extra = sentry__value_clone(source->extra); - clone->attributes = sentry__value_clone(source->attributes); - clone->contexts = sentry__value_clone(source->contexts); - clone->propagation_context - = sentry__value_clone(source->propagation_context); - clone->breadcrumbs = sentry__ringbuffer_clone(source->breadcrumbs); - clone->dynamic_sampling_context - = sentry__value_clone(source->dynamic_sampling_context); - if (sentry_value_is_frozen(source->dynamic_sampling_context)) { - sentry_value_freeze(clone->dynamic_sampling_context); - } - clone->level = source->level; - clone->last_event_id = source->last_event_id; - clone->client_sdk = sentry__value_clone(source->client_sdk); - clone->attachments = sentry__attachments_clone(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; + sentry__rwlock_init(&clone->rwlock); + SENTRY_SCOPE_READ_LOCK (source) { + clone->release = sentry__value_clone(source->release); + clone->environment = sentry__value_clone(source->environment); + clone->transaction = sentry__value_clone(source->transaction); + clone->fingerprint = sentry__value_clone(source->fingerprint); + clone->user = sentry__value_clone(source->user); + clone->tags = sentry__value_clone(source->tags); + clone->extra = sentry__value_clone(source->extra); + clone->attributes = sentry__value_clone(source->attributes); + clone->contexts = sentry__value_clone(source->contexts); + clone->propagation_context + = sentry__value_clone(source->propagation_context); + clone->breadcrumbs = sentry__ringbuffer_clone(source->breadcrumbs); + clone->dynamic_sampling_context + = sentry__value_clone(source->dynamic_sampling_context); + if (sentry_value_is_frozen(source->dynamic_sampling_context)) { + sentry_value_freeze(clone->dynamic_sampling_context); + } + clone->level = source->level; + clone->last_event_id = source->last_event_id; + clone->client_sdk = sentry__value_clone(source->client_sdk); + clone->attachments = sentry__attachments_clone(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; } @@ -247,7 +303,7 @@ generate_propagation_context(sentry_value_t propagation_context) void sentry__scope_update_dsc(sentry_scope_t *scope, const sentry_options_t *options) { - sentry_value_decref(scope->data->dynamic_sampling_context); + sentry_scope_data_t *data = scope->data; sentry_value_t dsc = sentry_value_new_object(); if (options->dsn) { @@ -264,22 +320,33 @@ sentry__scope_update_dsc(sentry_scope_t *scope, const sentry_options_t *options) sentry_value_set_by_key( dsc, "sample_rate", sentry_value_new_double(1.0)); } - sentry_value_t sample_rand = sentry_value_get_by_key( - sentry_value_get_by_key(scope->data->propagation_context, "trace"), - "sample_rand"); - sentry_value_set_by_key(dsc, "sample_rand", sample_rand); - sentry_value_incref(sample_rand); - sentry_value_set_by_key( - dsc, "release", sentry_value_new_string(scope->data->release)); - sentry_value_set_by_key( - dsc, "environment", sentry_value_new_string(scope->data->environment)); - scope->data->dynamic_sampling_context = dsc; + 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; @@ -288,6 +355,7 @@ init_scope(sentry_scope_t *scope, sentry_scope_data_t *data) scope->num_observers = 0; scope->is_notifying = 0; scope->pending_flush = false; + sentry__mutex_init(&scope->observers_lock); scope->one_shot = false; return true; } @@ -301,20 +369,12 @@ get_scope(void) memset(&g_scope, 0, sizeof(sentry_scope_t)); memset(&g_scope_data, 0, sizeof(sentry_scope_data_t)); + sentry__rwlock_init(&g_scope_data.rwlock); init_scope_data(&g_scope_data); - init_scope(&g_scope, &g_scope_data); - g_scope.data->user = sentry_value_new_object(); - sentry_value_set_by_key( - g_scope.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(g_scope.data->contexts, "wine", wine_context); - } else { - sentry_value_decref(wine_context); + if (!init_scope(&g_scope, &g_scope_data)) { + return &g_scope; } -#endif - g_scope.data->client_sdk = get_client_sdk(); + init_global_data(g_scope.data); g_scope_initialized = true; @@ -330,6 +390,7 @@ cleanup_observers(sentry_scope_t *scope) sentry_free(scope->observers); scope->observers = NULL; scope->num_observers = 0; + scope->is_notifying = 0; scope->pending_flush = false; } @@ -339,6 +400,42 @@ 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 @@ -346,41 +443,58 @@ 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_data(g_scope.data); + 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) { @@ -390,18 +504,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) { @@ -416,10 +518,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; } @@ -431,6 +535,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; } @@ -438,7 +543,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; } @@ -451,6 +562,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++) { @@ -461,13 +573,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; } @@ -476,9 +591,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; } @@ -495,6 +612,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 * @@ -514,19 +632,29 @@ sentry_scope_new(void) 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); } } @@ -536,7 +664,7 @@ sentry_local_scope_new(void) { sentry_scope_t *scope = sentry_scope_new(); if (scope) { - scope->one_shot = true; + sentry__scope_set_one_shot(scope, true); } return scope; } @@ -544,35 +672,39 @@ sentry_local_scope_new(void) void sentry__scope_apply_options(sentry_scope_t *scope, sentry_options_t *options) { - if (options->sdk_name) { - sentry_value_t sdk_name = sentry_value_new_string(options->sdk_name); - sentry_value_set_by_key(scope->data->client_sdk, "name", sdk_name); - } - sentry_value_t integrations - = sentry_value_get_by_key(scope->data->client_sdk, "integrations"); - for (size_t i = 0; i < options->num_integrations; i++) { - const char *name = options->integrations[i]->name; - if (!name) { - continue; + 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); } - if (sentry_value_is_null(integrations)) { - integrations = sentry_value_new_list(); - sentry_value_set_by_key( - scope->data->client_sdk, "integrations", integrations); + 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_append(integrations, sentry_value_new_string(name)); - } - sentry_value_freeze(scope->data->client_sdk); - generate_propagation_context(scope->data->propagation_context); - scope->data->release = sentry__string_clone(options->release); - scope->data->environment = sentry__string_clone(options->environment); - sentry_value_decref(scope->data->attachments); - scope->data->attachments = options->attachments; - options->attachments = sentry_value_new_null(); - - sentry__ringbuffer_set_max_size( - scope->data->breadcrumbs, options->max_breadcrumbs); - + 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); } @@ -623,72 +755,100 @@ sentry_scope_clone(const sentry_scope_t *scope) sentry_value_t sentry__scope_load_propagation_context(const sentry_scope_t *scope) { - return sentry__value_clone(scope->data->propagation_context); + 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_set_propagation_context( sentry_scope_t *scope, const char *key, sentry_value_t value) { - sentry_value_set_by_key(scope->data->propagation_context, key, value); + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + sentry_value_set_by_key(scope->data->propagation_context, key, value); + } } void sentry__scope_regenerate_propagation_context(sentry_scope_t *scope) { - generate_propagation_context(scope->data->propagation_context); + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + generate_propagation_context(scope->data->propagation_context); + } } sentry_value_t sentry__scope_load_trace_context(const sentry_scope_t *scope) { - return sentry__value_clone( - sentry_value_get_by_key(scope->data->propagation_context, "trace")); + 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")); + } + return trace_context; } void sentry__scope_set_trace_context( sentry_scope_t *scope, const char *key, sentry_value_t value) { - sentry_value_set_by_key( - sentry_value_get_by_key(scope->data->propagation_context, "trace"), key, - value); + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + sentry_value_set_by_key( + sentry_value_get_by_key(scope->data->propagation_context, "trace"), + key, value); + } } bool sentry__scope_is_trace_managed(const sentry_scope_t *scope) { - return scope->data->trace_managed; + 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) { - scope->data->trace_managed = managed; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + scope->data->trace_managed = managed; + } } sentry_value_t sentry__scope_load_dsc(const sentry_scope_t *scope) { - return sentry__value_clone(scope->data->dynamic_sampling_context); + 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_value_foreach_key_value( - scope->data->dynamic_sampling_context, callback, 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_decref(scope->data->dynamic_sampling_context); sentry_value_t dsc = sentry_value_new_object(); sentry__value_merge_objects(dsc, incoming); sentry_value_freeze(dsc); - scope->data->dynamic_sampling_context = dsc; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + sentry__value_replace(&scope->data->dynamic_sampling_context, dsc); + } } #if !defined(SENTRY_PLATFORM_NX) @@ -794,17 +954,16 @@ sentry__symbolize_stacktrace(sentry_value_t stacktrace) } #endif -static sentry_value_t -get_span_or_transaction(const sentry_scope_t *scope) +#ifdef SENTRY_UNITTEST +bool +sentry__scope_has_observers(const sentry_scope_t *scope) { - if (scope->data->span) { - return scope->data->span->inner; - } else if (scope->data->transaction_object) { - return scope->data->transaction_object->inner; - } else { - return sentry_value_new_null(); - } + sentry__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 void sentry__scope_apply_to_event(const sentry_scope_t *scope, @@ -819,6 +978,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)) { \ @@ -835,55 +1000,81 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, PLACE_STRING("platform", "native"); - PLACE_STRING("release", scope->data->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->data->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->data->level)); + SET("level", sentry__value_new_level(sentry__scope_get_level(scope))); } - if (sentry_value_get_type(scope->data->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->data->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->data->user) > 0) { - PLACE_CLONED_VALUE("user", scope->data->user); + } else if (sentry_value_get_length(user) > 0) { + PLACE_CLONED_VALUE("user", user); } } - PLACE_CLONED_VALUE("fingerprint", scope->data->fingerprint); - PLACE_STRING("transaction", scope->data->transaction); - PLACE_VALUE("sdk", scope->data->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->data->tags)) { - PLACE_CLONED_VALUE("tags", scope->data->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->data->tags); - } - sentry_value_t event_extra = sentry_value_get_by_key(event, "extra"); - if (sentry_value_is_null(event_extra)) { - if (!sentry_value_is_null(scope->data->extra)) { - PLACE_CLONED_VALUE("extra", scope->data->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->data->extra); } bool is_transaction = sentry__event_is_transaction(event); - sentry_value_t contexts = sentry__value_clone(scope->data->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"); } @@ -893,7 +1084,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_load_span_or_transaction(scope); scope_trace = sentry__value_get_trace_context(scoped_txn_or_span); } if (!sentry_value_is_null(scope_trace)) { @@ -909,6 +1100,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"); @@ -916,7 +1108,10 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, if (!is_transaction && sentry_value_is_null(scope_trace) && sentry_value_is_null( sentry_value_get_by_key(event_contexts, "trace"))) { - sentry__value_merge_objects(contexts, scope->data->propagation_context); + sentry_value_t propagation_context + = 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); @@ -929,7 +1124,7 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, sentry_value_t event_breadcrumbs = sentry_value_get_by_key(event, "breadcrumbs"); sentry_value_t scope_breadcrumbs - = sentry__ringbuffer_to_list(scope->data->breadcrumbs); + = sentry__scope_breadcrumbs_to_list(scope); sentry_value_set_by_key(event, "breadcrumbs", sentry__value_merge_breadcrumbs(event_breadcrumbs, scope_breadcrumbs, options->max_breadcrumbs)); @@ -953,6 +1148,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 @@ -961,43 +1157,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->data->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) { - return sentry__ringbuffer_to_list(scope->data->breadcrumbs); + 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) { - return sentry_value_incref(scope->data->user); + 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->data->user); - scope->data->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) { - return sentry__value_clone(scope->data->tags); + 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->data->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); } } @@ -1006,11 +1227,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->data->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 @@ -1035,7 +1271,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->data->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); } } @@ -1044,44 +1284,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->data->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); } - sentry_free(k); + 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) { - return sentry__value_clone(scope->data->extra); + sentry_value_t extra = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + extra = sentry__value_clone(scope->data->extra); + } + return extra; } void sentry_scope_set_extra( sentry_scope_t *scope, const char *key, sentry_value_t value) { - if (sentry_value_set_by_key(scope->data->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->data->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->data->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); } } @@ -1090,12 +1361,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->data->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 @@ -1116,58 +1390,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->data->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) { - return sentry__value_clone(scope->data->attributes); + 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->data->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->data->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) { - return sentry__value_clone(scope->data->contexts); + 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->data->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->data->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->data->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); } } @@ -1176,12 +1490,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->data->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); + } + if (removed_key) { + SENTRY_SCOPE_NOTIFY(scope, remove_context, removed_key); } - sentry_free(k); + sentry_free(removed_key); } void @@ -1196,35 +1513,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->data->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->data->contexts, k, key_len, value) - != 0) { - return; - } - } else { - sentry__value_merge_objects(value, context); - if (sentry__value_set_by_key_owned( - scope->data->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->data->release); - scope->data->release = sentry__string_clone_n(release, release_len); - sentry_value_set_by_key(scope->data->dynamic_sampling_context, "release", - sentry_value_new_string(scope->data->release)); - SENTRY_SCOPE_NOTIFY(scope, set_release, scope->data->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 @@ -1237,12 +1562,15 @@ void sentry_scope_set_environment_n( sentry_scope_t *scope, const char *environment, size_t environment_len) { - sentry_free(scope->data->environment); - scope->data->environment - = sentry__string_clone_n(environment, environment_len); - sentry_value_set_by_key(scope->data->dynamic_sampling_context, - "environment", sentry_value_new_string(scope->data->environment)); - SENTRY_SCOPE_NOTIFY(scope, set_environment, scope->data->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 @@ -1256,15 +1584,17 @@ void sentry_scope_set_transaction_n( sentry_scope_t *scope, const char *transaction, size_t transaction_len) { - sentry_free(scope->data->transaction); - scope->data->transaction - = sentry__string_clone_n(transaction, transaction_len); - - if (scope->data->transaction_object) { - sentry_transaction_set_name_n( - scope->data->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->data->transaction); + SENTRY_SCOPE_NOTIFY_OWNED(scope, set_transaction, value); } void @@ -1277,7 +1607,11 @@ sentry_scope_set_transaction(sentry_scope_t *scope, const char *transaction) sentry_value_t sentry__scope_ref_fingerprint(const sentry_scope_t *scope) { - return sentry_value_incref(scope->data->fingerprint); + sentry_value_t fingerprint = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + fingerprint = sentry_value_incref(scope->data->fingerprint); + } + return fingerprint; } void @@ -1290,9 +1624,7 @@ sentry__scope_set_fingerprint_va( fingerprint_value, sentry_value_new_string(fingerprint)); } - sentry_value_decref(scope->data->fingerprint); - scope->data->fingerprint = fingerprint_value; - SENTRY_SCOPE_NOTIFY(scope, set_fingerprint, fingerprint_value); + sentry_scope_set_fingerprints(scope, fingerprint_value); } void @@ -1345,35 +1677,50 @@ sentry_scope_set_fingerprints( return; } - sentry_value_decref(scope->data->fingerprint); - scope->data->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->data->fingerprint); - scope->data->fingerprint = sentry_value_new_null(); - SENTRY_SCOPE_NOTIFY(scope, set_fingerprint, scope->data->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) { - return scope->data->level; + 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) { - return sentry_value_incref(scope->data->client_sdk); + 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->data->level = level; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + scope->data->level = level; + } SENTRY_SCOPE_NOTIFY(scope, set_level, level); } @@ -1393,17 +1740,25 @@ sentry_scope_set_span(sentry_scope_t *scope, sentry_span_t *span) sentry_value_t sentry__scope_load_attachments(const sentry_scope_t *scope) { - return sentry_value_incref(scope->data->attachments); + sentry_value_t attachments = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + attachments = sentry__attachments_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->data->attachments); - sentry_value_t added - = sentry__attachments_add(&scope->data->attachments, attachment); - if (!sentry_value_is_null(added) - && sentry_value_get_length(scope->data->attachments) > len) { + 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; @@ -1412,8 +1767,11 @@ sentry__scope_add_attachment(sentry_scope_t *scope, sentry_value_t attachment) sentry_value_t sentry__scope_take_attachments(sentry_scope_t *scope) { - sentry_value_t attachments = scope->data->attachments; - scope->data->attachments = sentry_value_new_list(); + 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; } @@ -1425,8 +1783,11 @@ sentry_scope_remove_attachment( return; } - sentry_value_t removed - = sentry__attachments_remove(scope->data->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); } @@ -1450,52 +1811,61 @@ sentry_scope_add_attachment(sentry_scope_t *scope, sentry_value_t attachment) sentry_transaction_t * sentry__scope_ref_transaction_object(const sentry_scope_t *scope) { - sentry_transaction_t *transaction = scope->data->transaction_object; - sentry__transaction_incref(transaction); + 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 *tx) + sentry_scope_t *scope, sentry_transaction_t *transaction) { - sentry__span_decref(scope->data->span); - scope->data->span = NULL; - // incref before decref, so rebinding the same object cannot free it - sentry__transaction_incref(tx); - sentry__transaction_decref(scope->data->transaction_object); - scope->data->transaction_object = tx; + 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) { - if (scope->data->transaction_object == transaction) { - sentry__transaction_decref(scope->data->transaction_object); - scope->data->transaction_object = NULL; - return true; + 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 false; + return removed; } bool -sentry__scope_remove_transaction_value(sentry_scope_t *scope, sentry_value_t tx) -{ - if (scope->data->transaction_object) { - sentry_value_t scope_tx = scope->data->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->data->transaction_object); +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; - return true; } } - return false; + sentry__transaction_decref(transaction_object); + return transaction_object != NULL; } bool @@ -1503,9 +1873,12 @@ sentry__scope_restore_transaction_object( sentry_scope_t *scope, sentry_transaction_t *transaction) { bool restored = false; - if (!scope->data->transaction_object && transaction) { - scope->data->transaction_object = transaction; - restored = true; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + if (!scope->data->transaction_object && !scope->data->span + && transaction) { + scope->data->transaction_object = transaction; + restored = true; + } } return restored; } @@ -1513,65 +1886,82 @@ sentry__scope_restore_transaction_object( sentry_span_t * sentry__scope_ref_span(const sentry_scope_t *scope) { - sentry_span_t *span = scope->data->span; - sentry__span_incref(span); + 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_load_span_or_transaction(const sentry_scope_t *scope) { - return sentry__value_clone(get_span_or_transaction(scope)); + sentry_value_t value = sentry_value_new_null(); + SENTRY_SCOPE_READ_LOCK (scope->data) { + if (scope->data->span) { + value = sentry__value_clone(scope->data->span->inner); + } else if (scope->data->transaction_object) { + value = sentry__value_clone(scope->data->transaction_object->inner); + } + } + return value; } void sentry__scope_set_span(sentry_scope_t *scope, sentry_span_t *span) { - sentry__transaction_decref(scope->data->transaction_object); - scope->data->transaction_object = NULL; - // incref before decref, so rebinding the same object cannot free it sentry__span_incref(span); - sentry__span_decref(scope->data->span); - scope->data->span = 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) { - if (scope->data->span == span) { - sentry__span_decref(scope->data->span); - scope->data->span = NULL; - return true; + bool removed = false; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + if (span && scope->data->span == span) { + scope->data->span = NULL; + removed = true; + } } - return false; + if (removed) { + sentry__span_decref(span); + } + return removed; } bool sentry__scope_remove_span_value(sentry_scope_t *scope, sentry_value_t span) { - if (scope->data->span) { - sentry_value_t scope_span = scope->data->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->data->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; - return true; } } - return false; + 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; - if (!scope->data->span && span) { - scope->data->span = span; - restored = true; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + if (!scope->data->span && !scope->data->transaction_object && span) { + scope->data->span = span; + restored = true; + } } return restored; } @@ -1579,24 +1969,31 @@ sentry__scope_restore_span(sentry_scope_t *scope, sentry_span_t *span) sentry_value_t sentry__scope_ref_release(const sentry_scope_t *scope) { - return scope->data->release ? sentry_value_new_string(scope->data->release) - : sentry_value_new_null(); + 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) { - return scope->data->environment - ? sentry_value_new_string(scope->data->environment) - : sentry_value_new_null(); + 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) { - return scope->data->transaction - ? sentry_value_new_string(scope->data->transaction) - : sentry_value_new_null(); + 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 @@ -1668,49 +2065,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->data->attributes); - - // a span on the scope MUST take precedence over the propagation context - sentry_value_t trace_id = sentry_value_get_by_key( - sentry_value_get_by_key(scope->data->propagation_context, "trace"), - "trace_id"); - - sentry_value_t parent_span_id = sentry_value_new_object(); - if (scope->data->transaction_object) { - sentry_value_t span_id = sentry_value_get_by_key( - scope->data->transaction_object->inner, "span_id"); - sentry_value_incref(span_id); - sentry_value_set_by_key(parent_span_id, "value", span_id); - trace_id = sentry_value_get_by_key( - scope->data->transaction_object->inner, "trace_id"); - } else if (scope->data->span) { - sentry_value_t span_id - = sentry_value_get_by_key(scope->data->span->inner, "span_id"); - sentry_value_incref(span_id); - sentry_value_set_by_key(parent_span_id, "value", span_id); - trace_id - = sentry_value_get_by_key(scope->data->span->inner, "trace_id"); - } - sentry_value_set_by_key( - parent_span_id, "type", sentry_value_new_string("string")); - if ((scope->data->transaction_object || scope->data->span) - && sentry_value_is_null(sentry_value_get_by_key( - attributes, "sentry.trace.parent_span_id"))) { + 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->data->user)) { - sentry_value_t user_id - = sentry_value_get_by_key(scope->data->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( @@ -1718,61 +2143,56 @@ sentry__scope_apply_to_telemetry(const sentry_scope_t *scope, } sentry_value_t user_username - = sentry_value_get_by_key(scope->data->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->data->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->data->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->data->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->data->environment), "string", - "sentry.environment"); + sentry_value_incref(environment), "string", "sentry.environment"); } - if (scope->data->release) { - sentry__value_add_attribute(attributes, - sentry_value_new_string(scope->data->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->data->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) { - scope->data->last_event_id = event_id; + SENTRY_SCOPE_WRITE_LOCK (scope->data) { + scope->data->last_event_id = event_id; + } } } @@ -1783,7 +2203,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->data->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 ff44cda6c..018ab341f 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); @@ -55,8 +56,10 @@ typedef struct sentry_scope_data_s sentry_scope_data_t; * This represents the current scope. */ struct sentry_scope_s { + 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; @@ -84,14 +87,21 @@ 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_lock(void); +sentry_scope_t *sentry__scope_getref(void); /** - * Release the lock on the global scope. + * Increment the refcount and return the scope pointer. */ -void sentry__scope_unlock(void); +sentry_scope_t *sentry__scope_incref(sentry_scope_t *scope); + +/** + * Decrement the refcount and free the scope when the last reference is + * released. + */ +void sentry__scope_decref(sentry_scope_t *scope); /** * This will free all the data attached to the global scope @@ -101,17 +111,19 @@ 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 @@ -140,8 +152,11 @@ 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 reference to the scope attachment list. - * The caller must release it with `sentry_value_decref`. + * 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( @@ -190,18 +205,17 @@ bool sentry__scope_is_trace_managed(const sentry_scope_t *scope); void sentry__scope_set_trace_managed(sentry_scope_t *scope, bool managed); /** - * These are convenience macros to automatically lock/unlock the global scope - * inside a code block. + * These are convenience macros to access the global scope inside a code block. */ #define SENTRY_WITH_SCOPE(Scope) \ - for (const sentry_scope_t *Scope = sentry__scope_lock(); Scope; \ - sentry__scope_unlock(), Scope = NULL) + for (const sentry_scope_t *Scope = sentry__scope_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. @@ -216,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); @@ -225,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); @@ -285,3 +299,8 @@ void sentry__scope_capture_envelope(sentry_scope_t *scope, const sentry_options_t *options); #endif + +// this is only used in unit tests +#ifdef SENTRY_UNITTEST +bool sentry__scope_has_observers(const sentry_scope_t *scope); +#endif 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 bd75f1b1c..ba328b839 100644 --- a/tests/unit/test_attachments.c +++ b/tests/unit/test_attachments.c @@ -24,13 +24,11 @@ add_scope_attachments(sentry_envelope_t *envelope) } 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) @@ -146,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"); @@ -160,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"); @@ -168,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"); @@ -195,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); diff --git a/tests/unit/test_concurrency.c b/tests/unit/test_concurrency.c index 83088629f..3a5e0154d 100644 --- a/tests/unit/test_concurrency.c +++ b/tests/unit/test_concurrency.c @@ -278,10 +278,11 @@ SENTRY_TEST(concurrent_reinstall) sentry_options_set_backend(options, backend); sentry_init(options); - // Holding the scope lock simulates an observer callback in progress. - // Backend reinstall must wait until that callback finishes. - sentry_scope_t *scope = sentry__scope_lock(); + // Holding the scope observer lock simulates an observer callback in + // progress. Backend reinstall must wait until that callback finishes. + sentry_scope_t *scope = sentry__scope_getref(); TEST_ASSERT(!!scope); + sentry__mutex_lock(&scope->observers_lock); sentry_threadid_t thread; sentry__thread_init(&thread); TEST_ASSERT( @@ -297,9 +298,100 @@ SENTRY_TEST(concurrent_reinstall) } TEST_CHECK_INT_EQUAL(sentry__atomic_fetch(&state.shutdowns), 0); - sentry__scope_unlock(); + sentry__mutex_unlock(&scope->observers_lock); + sentry__scope_finish(scope, false); sentry__thread_join(thread); sentry__thread_free(&thread); TEST_CHECK_INT_EQUAL(sentry__atomic_fetch(&state.shutdowns), 1); 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_scope.c b/tests/unit/test_scope.c index 9998a9e76..118212bc6 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -498,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); } @@ -727,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); } @@ -1515,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 { @@ -1545,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; } @@ -1567,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; } @@ -1647,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; } @@ -1655,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(); } @@ -1794,7 +1825,7 @@ SENTRY_TEST(scope_observer_clear) sentry_scope_clear(scope); TEST_CHECK(d.was_cleared); - TEST_CHECK_INT_EQUAL(scope->num_observers, 1); + TEST_CHECK(sentry__scope_has_observers(scope)); TEST_CHECK(scope_value_get_length(sentry__scope_load_tags, scope) == 0); d.was_called = false; @@ -1821,8 +1852,7 @@ SENTRY_TEST(scope_observer_clear) sentry_scope_set_tag(scope, "during", "notify"); TEST_CHECK(observer_data.was_called); TEST_CHECK(observer_data.was_cleared); - TEST_CHECK_INT_EQUAL(scope->is_notifying, 0); - TEST_CHECK_INT_EQUAL(scope->num_observers, 1); + TEST_CHECK(sentry__scope_has_observers(scope)); TEST_CHECK(scope_value_get_length(sentry__scope_load_tags, scope) == 0); sentry_value_decref(observer_data.tags); @@ -2051,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(); } @@ -2073,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(); } @@ -2096,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(); } @@ -2193,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() }; @@ -2201,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); @@ -2580,12 +2645,19 @@ 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 @@ -2610,7 +2682,7 @@ SENTRY_TEST(scope_clone_independence) sentry_scope_t *clone = sentry_scope_clone(scope); // A clone is reusable, never one-shot. - TEST_CHECK(!clone->one_shot); + TEST_CHECK(!sentry__scope_is_one_shot(clone)); // The clone carries over the source's data. sentry_value_t clone_tag @@ -2715,6 +2787,66 @@ SENTRY_TEST(scope_clone_preserves_data) sentry_scope_free(scope); } +SENTRY_TEST(scope_attachments) +{ + sentry_scope_t *scope = sentry_scope_new(); + sentry_uuid_t first_id = sentry_scope_add_attachment( + scope, sentry_attachment_from_bytes("first", 5, "first.txt")); + sentry_uuid_t second_id = sentry_scope_add_attachment( + scope, sentry_attachment_from_bytes("second", 6, "second.txt")); + TEST_CHECK(!sentry_uuid_is_nil(&first_id)); + TEST_CHECK(!sentry_uuid_is_nil(&second_id)); + + sentry_value_t snapshot = sentry__scope_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); @@ -2751,6 +2883,56 @@ SENTRY_TEST(scope_clone_shares_span) sentry_close(); } +SENTRY_TEST(scope_restore_trace) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_init(options); + + sentry_scope_t *scope = sentry_scope_new(); + sentry_transaction_context_t *tx_ctx + = sentry_transaction_context_new("txn", NULL); + sentry_transaction_t *tx + = sentry_transaction_start(tx_ctx, sentry_value_new_null()); + sentry_span_t *span = sentry_transaction_start_child(tx, "op", NULL); + + sentry__scope_set_transaction_object(scope, tx); + TEST_CHECK(!sentry__scope_restore_span(scope, span)); + + sentry_span_t *scope_span = sentry__scope_ref_span(scope); + TEST_CHECK(scope_span == NULL); + sentry__span_decref(scope_span); + sentry_transaction_t *scope_tx + = sentry__scope_ref_transaction_object(scope); + TEST_CHECK(scope_tx == tx); + sentry__transaction_decref(scope_tx); + + sentry_scope_free(scope); + sentry__span_decref(span); + sentry__transaction_decref(tx); + + scope = sentry_scope_new(); + tx_ctx = sentry_transaction_context_new("txn", NULL); + tx = sentry_transaction_start(tx_ctx, sentry_value_new_null()); + span = sentry_transaction_start_child(tx, "op", NULL); + + sentry__scope_set_span(scope, span); + TEST_CHECK(!sentry__scope_restore_transaction_object(scope, tx)); + + scope_tx = sentry__scope_ref_transaction_object(scope); + TEST_CHECK(scope_tx == NULL); + sentry__transaction_decref(scope_tx); + scope_span = sentry__scope_ref_span(scope); + TEST_CHECK(scope_span == span); + sentry__span_decref(scope_span); + + sentry_scope_free(scope); + sentry__span_decref(span); + sentry__transaction_decref(tx); + + sentry_close(); +} + SENTRY_TEST(scope_clear) { SENTRY_TEST_OPTIONS_NEW(options); diff --git a/tests/unit/test_value.c b/tests/unit/test_value.c index d0b8ea611..9f3056d93 100644 --- a/tests/unit/test_value.c +++ b/tests/unit/test_value.c @@ -2323,3 +2323,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 4565226d3..11f7643be 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -358,6 +358,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) @@ -371,6 +372,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) @@ -407,6 +409,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) @@ -553,6 +556,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)