Skip to content

refactor(diagnostics): Guard diagnostic counters with a lock instead of a queue - #539

Open
abelonogov-ld wants to merge 1 commit into
v11from
andrey/diagnostic-cache-lock
Open

abelonogov-ld wants to merge 1 commit into
v11from
andrey/diagnostic-cache-lock

Conversation

@abelonogov-ld

@abelonogov-ld abelonogov-ld commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

DiagnosticCache serialized its counters with a private serial DispatchQueue and sync. Every dropped event (incrementDroppedEventCount) and every delivered batch (recordEventsInLastBatch) goes through it, on the caller's thread. With the event-durability work, a full event store counts every refused evaluation here, so a queue hop per counter increment is visible at that rate.

This replaces the queue with the existing UnfairLock (os_unfair_lock on Darwin, NSLock elsewhere), and adds UnfairLock.withLock(_:).

Why a closure-based withLock is free

withLock is @inline(__always) and takes a non-escaping closure. With -O -wmo, it compiles to the same assembly as calling lock()/unlock() by hand: no closure context allocation and no retain/release. Only -Onone builds pay for a real call.

On non-Darwin platforms, UnfairLock is NSLock, and the matching withLock is declared on NSLock. That way it doesn't depend on Foundation versions that ship NSLocking.withLock.

Behavior

There is no behavior change: the same state and the same reset semantics, only a different mutual exclusion primitive.

Testing

The full iOS suite passes (684 tests).


Note

Overview
DiagnosticCache no longer serializes in-memory diagnostic counters through a private serial DispatchQueue and sync. It now guards the same fields with UnfairLock and a new withLock helper, motivated by high-frequency incrementDroppedEventCount calls when the event store is full.

UnfairLock gains an @inline(__always) withLock on Darwin; non-Darwin builds get the same API via an NSLock extension so callers do not depend on newer Foundation NSLocking.withLock. Documented intent is unchanged reset and counter semantics—only a cheaper mutual-exclusion primitive on the evaluation thread.

Reviewed by Cursor Bugbot for commit 60601c1. Bugbot is set up for automated code reviews on this repo. Configure here.

…of a queue

Every dropped event and every delivered batch goes through DiagnosticCache, and
a DispatchQueue.sync for a counter increment costs more than the increment.
Adds UnfairLock.withLock, which inlines to the same code as lock()/unlock().

Co-authored-by: Cursor <cursoragent@cursor.com>
@abelonogov-ld
abelonogov-ld requested a review from a team as a code owner October 9, 2026 14:21

This branch has not been deployed

No deployments
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.

1 participant