Skip to content

[WIP] perf: use recursive rwlock for global scope#1877

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

[WIP] perf: use recursive rwlock for global scope#1877
jpnurmi wants to merge 2 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 global scope readers while preserving recursive write semantics for existing SDK call paths.

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

Allow concurrent global scope readers while preserving recursive write
semantics for existing SDK call paths.
@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
Comment thread src/sentry_sync.h
@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.

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.

@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 495cd85. Configure here.

Comment thread src/sentry_sync.h
lock->readers++;
sentry__cond_wake(&lock->reader_cond);
sentry__mutex_unlock(&lock->mutex);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nested reads deadlock under writers

High Severity

sentry__rwlock_read_lock treats writer-owned nesting as recursive, but a non-writer that already holds a read lock can block again when waiting_writers > 0. That thread still owns readers, so a waiting writer never proceeds and nested SENTRY_WITH_SCOPE (or read-then-write) deadlocks. The old recursive mutex allowed these nests; rwlock_recursion only covers the no-writer case.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 495cd85. Configure here.

Comment thread src/sentry_scope.c
{
g_scope_flush_pending = true;
sentry__scope_write_unlock();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

NO_FLUSH can still flush

Medium Severity

sentry__scope_write_unlock flushes whenever g_scope_flush_pending is set on the outermost unlock. SENTRY_WITH_SCOPE_MUT_NO_FLUSH uses that path, so an inner SENTRY_WITH_SCOPE_MUT can force a flush on the outer NO_FLUSH exit. That breaks the no-flush contract used by paths like sentry_add_breadcrumb.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 495cd85. Configure here.

Comment thread src/sentry_sync.h
Comment on lines +458 to +460
lock->writer_depth++;
sentry__mutex_unlock(&lock->mutex);
return;

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 sentry__rwlock_read_lock function doesn't check for waiting writers, allowing new readers to acquire the lock while a writer is queued, causing indefinite writer starvation.
Severity: HIGH

Suggested Fix

Modify sentry__rwlock_read_lock to block new readers while writers are queued. The wait condition should check for both active writers and waiting writers, for example: while (lock->writer_depth > 0 || lock->waiting_writers > 0).

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_sync.h#L458-L460

Potential issue: The `sentry__rwlock_read_lock` function does not check for
`waiting_writers` before granting a read lock. When no writer holds the lock
(`writer_depth == 0`) but one or more writers are queued (`waiting_writers > 0`), new
readers can bypass the wait condition and increment `readers`. This keeps the waiting
writer's condition (`while (lock->writer_depth > 0 || lock->readers > 0)`) from ever
being met under a continuous read load, causing indefinite writer starvation. As a
result, SDK calls that write scope data, such as `sentry_set_user` or `sentry_set_tag`,
can be starved indefinitely in high-throughput scenarios.

Did we get this right? 👍 / 👎 to inform future reviews.

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