Skip to content

[WIP] perf: use rwlock for scope data access - #1877

Open
jpnurmi wants to merge 22 commits into
jpnurmi/ref/attachmentsfrom
jpnurmi/perf/rwlock
Open

[WIP] perf: use rwlock for scope data access#1877
jpnurmi wants to merge 22 commits into
jpnurmi/ref/attachmentsfrom
jpnurmi/perf/rwlock

Conversation

@jpnurmi

@jpnurmi jpnurmi commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Warning

WIP    🚧🔨⏳⛔

Mostly curious what the AI review bots have to say for now...

Allow concurrent scope data readers.

Before

tests/benchmark.py::test_benchmark_logs[1] PASSED
Min 0.002ms, Max 0.002ms, Mean 0.002ms, Median 0.002ms, CPU 0.002ms                                                                                                                                                                                                                                      [ 25%]
tests/benchmark.py::test_benchmark_logs[8] PASSED
Min 0.026ms, Max 0.026ms, Mean 0.026ms, Median 0.026ms, CPU 0.008ms                                                                                                                                                                                                                                      [ 50%]
tests/benchmark.py::test_benchmark_logs[16] PASSED
Min 0.067ms, Max 0.067ms, Mean 0.067ms, Median 0.067ms, CPU 0.010ms                                                                                                                                                                                                                                      [ 75%]
tests/benchmark.py::test_benchmark_logs[32] PASSED
Min 0.121ms, Max 0.121ms, Mean 0.121ms, Median 0.121ms, CPU 0.010ms

After

tests/benchmark.py::test_benchmark_logs[1] PASSED
Min 0.004ms, Max 0.004ms, Mean 0.004ms, Median 0.004ms, CPU 0.004ms                                                                                                                                                                                                                                      [ 25%]
tests/benchmark.py::test_benchmark_logs[8] PASSED
Min 0.011ms, Max 0.011ms, Mean 0.011ms, Median 0.011ms, CPU 0.011ms                                                                                                                                                                                                                                      [ 50%]
tests/benchmark.py::test_benchmark_logs[16] PASSED
Min 0.024ms, Max 0.024ms, Mean 0.024ms, Median 0.024ms, CPU 0.022ms                                                                                                                                                                                                                                      [ 75%]
tests/benchmark.py::test_benchmark_logs[32] PASSED
Min 0.048ms, Max 0.048ms, Mean 0.048ms, Median 0.048ms, CPU 0.030ms

Close: #1862

@jpnurmi jpnurmi changed the title perf: use recursive rwlock for global scope [WIP] perf: use recursive rwlock for global scope Jul 16, 2026
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Fails
🚫 Please consider adding a changelog entry for the next release.

Instructions and example for changelog

Please add an entry to CHANGELOG.md to the "Unreleased" section. Make sure the entry includes this PR's number.

Example:

## Unreleased

### Features

- use rwlock for scope data access ([#1877](https://github.com/getsentry/sentry-native/pull/1877))

If none of the above apply, you can opt out of this check by adding #skip-changelog to the PR description or adding a skip-changelog label.

Generated by 🚫 dangerJS against dab304d

Comment thread src/sentry_sync.h Outdated
Comment thread src/sentry_sync.h Outdated
@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.13059% with 80 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.04%. Comparing base (ed216c7) to head (de14204).

Additional details and impacted files
@@                     Coverage Diff                     @@
##           jpnurmi/ref/attachments    #1877      +/-   ##
===========================================================
+ Coverage                    74.36%   75.04%   +0.68%     
===========================================================
  Files                          104      104              
  Lines                        25606    26453     +847     
  Branches                      4567     4693     +126     
===========================================================
+ Hits                         19042    19852     +810     
- Misses                        5269     5297      +28     
- Partials                      1295     1304       +9     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread src/sentry_sync.h Outdated
Comment thread src/sentry_scope.c
Comment thread src/sentry_sync.h Outdated
@jpnurmi
jpnurmi force-pushed the jpnurmi/perf/rwlock branch from 495cd85 to 05fe336 Compare August 7, 2026 14:42
@jpnurmi jpnurmi changed the title [WIP] perf: use recursive rwlock for global scope [WIP] perf: use rwlock for scope data access Aug 7, 2026
Comment thread src/sentry_scope.c
Comment thread src/sentry_options.h
Comment thread src/sentry_attachment.c
Comment thread src/backends/sentry_backend_breakpad.cpp
Comment thread src/sentry_scope.c
Comment on lines 1750 to +1755
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) { \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The release and environment fields are now explicitly set to null in the Dynamic Sampling Context (DSC) when not provided, instead of being omitted, which is a behavioral regression.
Severity: MEDIUM

Suggested Fix

Modify the logic that updates the Dynamic Sampling Context (DSC), such as in data_set_release, to check for null values. If the value is null, the key should be omitted from the DSC object rather than being set with a null value, restoring the previous behavior.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/sentry_scope.c#L1750-L1755

Potential issue: When `options->release` or `options->environment` are `NULL`, the new
code in `sentry__scope_apply_options` sets the corresponding keys in the Dynamic
Sampling Context (DSC) to an explicit `null` value. Previously, these keys would have
been omitted from the DSC. This change in behavior can affect DSC serialization and any
downstream consumers that expect absent keys instead of `null` values. While event
application is protected by a check (`PLACE_STRING_VALUE`), the DSC is not, leading to
this regression.

Comment thread src/sentry_scope.c
Comment on lines +261 to +271

cleanup_data(data);
init_data(data);

sentry_value_decref(data->propagation_context);
sentry_value_decref(data->dynamic_sampling_context);
data->propagation_context = propagation_context;
data->dynamic_sampling_context = dynamic_sampling_context;
data->trace_managed = trace_managed;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: If breadcrumb ringbuffer allocation fails in clear_data due to OOM, the scope is left in a silently degraded state where breadcrumbs no longer function.
Severity: LOW

Suggested Fix

Check the return value of sentry__ringbuffer_new within init_data. If the allocation fails, the function should handle the error, possibly by attempting to restore the old state or by logging the failure, rather than silently leaving data->breadcrumbs as NULL.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/sentry_scope.c#L253-L271

Potential issue: The `clear_data` function allocates a new breadcrumbs ringbuffer via
`sentry__ringbuffer_new` while holding a write lock. If this memory allocation fails
(e.g., under OOM conditions), `data->breadcrumbs` is set to `NULL` without any error
handling. Subsequent operations on breadcrumbs will silently fail as they are coded
defensively to handle a `NULL` ringbuffer, preventing a crash but leaving the scope in a
degraded state where breadcrumb functionality is lost without any notification.

Comment thread src/sentry_attachment.c
Comment thread src/sentry_scope.c
@jpnurmi
jpnurmi force-pushed the jpnurmi/perf/rwlock branch from 3c7c966 to 4948141 Compare August 12, 2026 04:55
@jpnurmi
jpnurmi changed the base branch from master to jpnurmi/ref/attachments August 12, 2026 04:56
jpnurmi added 15 commits August 12, 2026 08:33
Add a non-recursive sentry_rwlock_t abstraction backed by platform
reader/writer locks. Cover static and dynamic initialization, reader sharing,
writer exclusion, and mixed read/write stress in unit tests.
Convert the POSIX rwlock operations from statement macros to static inline
functions so callers can use them from scoped lock macro expressions.
Add a per-scope rwlock and use scoped read/write locking for simple by-value
fields. Keep borrowed scope properties unchanged for now.
Add ref-style scope accessors for user and fingerprint values so callers get
a retained reference that was incremented under the scope read lock. Guard
user and fingerprint replacement with the scope write lock and keep observer
notifications outside the locked section.
Store release, environment, and transaction as sentry_value_t fields
with retained scope accessors.

Pass value references through observer notifications and add a generic
consuming sentry__value_replace helper for scope value replacement.
Move scope payload access behind sentry_scope_data_t helpers so scope-level
code no longer reaches through scope->data to individual fields.
Track active scope accesses and delay cleanup until they finish, while
releasing the lifetime guard before backend scope flushing.
@jpnurmi
jpnurmi force-pushed the jpnurmi/perf/rwlock branch from 4948141 to 0b0e77c Compare August 12, 2026 06:33
Comment thread src/sentry_scope.c
Comment on lines +1224 to 1234
sentry__cond_wait(&g_scope_idle, &g_lock);
}
if (g_scope_initialized) {
g_scope_initialized = false;
cleanup_scope(&g_scope);
cleanup_global_data(g_scope.data);
g_scope.data = NULL;
cleanup_observers(&g_scope);
sentry__mutex_free(&g_scope.observers_lock);
}
sentry__mutex_unlock(&g_lock);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The condition variable g_scope_idle is initialized but never destroyed, causing a kernel handle leak on some Windows platforms.
Severity: LOW

Suggested Fix

Implement a cleanup path for the g_scope_idle condition variable. This can be done by adding a call to a new sentry__cond_free(&g_scope_idle) function within sentry__scope_cleanup(), ensuring it is only called when g_scope_idle_initialized is true. The sentry__cond_free function would need to be implemented to close the relevant handles on Windows.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/sentry_scope.c#L1219-L1234

Potential issue: The condition variable `g_scope_idle` is initialized on first use
within `sentry__scope_begin()` but is never destroyed. On pre-Vista Windows platforms,
the implementation of `sentry__cond_init` allocates kernel handles for a semaphore and
an event. Since there is no corresponding cleanup function called during shutdown (e.g.,
in `sentry__scope_cleanup`), these handles are leaked. This leads to a resource leak on
affected Windows versions.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0b0e77c. Configure here.

Comment thread src/sentry_scope.c
Comment thread src/sentry_scope.c
Comment thread src/sentry_scope.c
Comment on lines +1038 to +1044
data_set_release(sentry_scope_data_t *data, sentry_value_t value)
{
DATA_WRITE_LOCK (data) {
sentry__value_replace(&data->release, sentry_value_incref(value));
sentry_value_set_by_key(data->dynamic_sampling_context, "release",
sentry_value_incref(value));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Updates to release or environment via data_set_release() fail silently on the dynamic_sampling_context if it has been frozen, leading to inconsistent state.
Severity: MEDIUM

Suggested Fix

The functions data_set_release and data_set_environment should handle the case where the dynamic_sampling_context is frozen. Instead of ignoring the return value of sentry_value_set_by_key, the code could create a mutable copy of the DSC, apply the update, and then replace the frozen one. This would ensure that the DSC remains consistent with the top-level scope data.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/sentry_scope.c#L1038-L1044

Potential issue: When an application receives an upstream trace with Dynamic Sampling
Context (DSC) headers, `sentry__scope_freeze_dsc()` is called, freezing the
`data->dynamic_sampling_context`. Subsequent calls to `sentry_set_release()` or
`sentry_set_environment()` attempt to update this frozen DSC via
`sentry_value_set_by_key()`. This operation fails silently because the object is frozen,
but the top-level `data->release` or `data->environment` fields are updated
successfully. This creates an inconsistency where the DSC propagated to upstream
services will be missing the correct release and environment information, impacting
distributed tracing.

Also affects:

  • src/sentry_scope.c:1057~1064

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Logs: optimize performance

1 participant