Skip to content

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

Open
jpnurmi wants to merge 30 commits into
masterfrom
jpnurmi/perf/rwlock
Open

[WIP] perf: use rwlock for scope data access#1877
jpnurmi wants to merge 30 commits into
masterfrom
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 recursive rwlock for global scope ([#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 495cd85

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 91.56627% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.67%. Comparing base (d50befe) to head (495cd85).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1877      +/-   ##
==========================================
+ Coverage   72.66%   75.67%   +3.01%     
==========================================
  Files          85       90       +5     
  Lines       14698    21168    +6470     
  Branches     2512     3761    +1249     
==========================================
+ Hits        10680    16019    +5339     
- Misses       3704     4325     +621     
- Partials      314      824     +510     
🚀 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 added 24 commits July 22, 2026 11:37
Allow concurrent global scope readers while preserving recursive write
semantics for existing SDK call paths.
Block new readers while writers are queued and use separate condition
variables so writer wake-ups cannot be consumed by waiting readers.

Add coverage ensuring a queued writer runs before later readers.
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.
Since `sentry_value_new_string` calls `strlen` + `sentry_value_new_string_n`
anyway, we might as well store the length. This does not only save us from
inefficiently calling `strlen` later, but also implicitly adds support for
embedded NUL bytes for presenting attachments (#1945).
@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 Outdated
Comment thread src/sentry_options.h
Comment thread src/sentry_attachment.c
attachment, ATTACHMENT_PATH, sentry_value_new_string(path->path));
set_filename_from_path(attachment, path);
sentry__path_free(path);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Path update clobbers filename

Medium Severity

sentry__attachment_set_path also rewrites filename from the path basename. Crashpad’s ensure_unique_path can suffix colliding names on disk; that used to update only path and keep the original logical filename for envelopes.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 05fe336. Configure here.

Comment thread src/backends/sentry_backend_breakpad.cpp
jpnurmi added 6 commits August 7, 2026 16:57
Track active scope accesses and delay cleanup until they finish, while
releasing the lifetime guard before backend scope flushing.
Decrement owned values returned by attachment add helpers when callers only
need the attachment list to retain them.
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 on lines +475 to +485
for (size_t i = 0; i < len; i++) {
sentry_value_t existing
= sentry_value_get_by_index(*attachments_ptr, i);
if (attachment_dedupe_eq(existing, attachment)) {
sentry_value_decref(attachment);
return sentry_value_incref(existing);
}

next_ptr = &it->next;
}

*next_ptr = attachment;
return attachment;
sentry_value_append(*attachments_ptr, attachment);
return sentry_value_get_by_index_owned(*attachments_ptr, len);

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: Attachments are frozen when added to the scope, which causes later modifications by backends (e.g., setting a path) to silently fail, leading to incorrect attachment data.
Severity: HIGH

Suggested Fix

Do not freeze attachments when they are added to the scope via sentry__attachments_add. Removing the call to sentry_value_freeze will allow backends to modify the attachment object as expected, for instance, to set the correct file path.

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_attachment.c#L471-L485

Potential issue: Attachments are frozen using `sentry_value_freeze` when added to the
scope. However, backend functions like `crashpad_backend_add_attachment` later attempt
to modify these frozen attachments by calling `sentry__attachment_set_path`. This
modification calls `sentry_value_set_by_key`, which silently fails on frozen objects. As
a result, the attachment's path is not updated, leading to incorrect path information or
data loss for attachments processed by backends.

Also affects:

  • src/backends/sentry_backend_crashpad.cpp:1107~1192
  • src/backends/sentry_backend_native.c:929~968

@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 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

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

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3c7c966. Configure here.

Comment thread src/sentry_scope.c
// backend will do its own `WITH_SCOPE` internally.
sentry__mutex_unlock(&g_lock);
} else {
end_scope_access();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cleanup races with deferred scope flush

Medium Severity

finish_scope drops the scope access count before invoking flush_scope_func, but backends re-enter via SENTRY_WITH_SCOPE. That leaves a window where sentry__scope_cleanup can tear down or observe an idle scope, then the flush reinitializes an empty global scope after close or writes a blank crash snapshot.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3c7c966. Configure here.

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