Replace synchronized withReentrantLock#3715
Conversation
Performance metrics 🚀
|
| private boolean enabled; | ||
| private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); | ||
|
|
||
| public ActivityBreadcrumbsIntegration(final @NotNull Application application) { |
There was a problem hiding this comment.
To be honest I don't know why we had all the sychronized blocks in here, it seems to stem from here. I think we can remove it in future.
| public ActivityBreadcrumbsIntegration(final @NotNull Application application) { | |
| // TODO check if locking is even required at all for lifecycle methods | |
| public ActivityBreadcrumbsIntegration(final @NotNull Application application) { |
| try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { | ||
| // no-op | ||
| } |
There was a problem hiding this comment.
- I don't think we need the locking here
- Tell me how you automated that refactoring 😅
| try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { | |
| // no-op | |
| } | |
| // no-op |
There was a problem hiding this comment.
I had to take a look at each thing I refactored manually 😢 . My thinking was to make this more clear in case we replace the noop comment with some actual code in the future.
There was a problem hiding this comment.
Replaced the locking code with a comment that shall serve as a reminder
| private final Collection<E> collection; | ||
| /** The object to lock on, needed for List/SortedSet views */ | ||
| final Object lock; | ||
| final AutoClosableReentrantLock lock; |
There was a problem hiding this comment.
could this be private as well?
There was a problem hiding this comment.
No because it's used in SynchronizedQueue. The same is basically true for any non final class since you have to use the right lock instead of just relying on the synchronized keyword on methods.
…ityBreadcrumbsIntegration.java Co-authored-by: Markus Hintersteiner <markus.hintersteiner@sentry.io>
Instructions and example for changelogPlease add an entry to Example: ## Unreleased
- Replace `synchronized` with`ReentrantLock` ([#3715](https://github.com/getsentry/sentry-java/pull/3715))If none of the above apply, you can opt out of this check by adding |
AutoClosableReentrantLock extended ReentrantLock, so every SDK object holding one allocated a ReentrantLock (and its AbstractQueuedSynchronizer) eagerly in its field initializer. A customer Perfetto trace showed ~81 such allocations on the main thread during SentryAndroid.init, many for locks that are never acquired during init. Hold the ReentrantLock internally and create it lazily on first acquire(), using an AtomicReferenceFieldUpdater CAS so creation stays atomic and Loom-friendly (no synchronized, preserving #3715). Every call site uses acquire() only, so dropping the ReentrantLock superclass touches no caller. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AutoClosableReentrantLock extended ReentrantLock, so every SDK object holding one allocated a ReentrantLock (and its AbstractQueuedSynchronizer) eagerly in its field initializer. A customer Perfetto trace showed ~81 such allocations on the main thread during SentryAndroid.init, many for locks that are never acquired during init. Hold the ReentrantLock internally and create it lazily on first acquire(), using an AtomicReferenceFieldUpdater CAS so creation stays atomic and Loom-friendly (no synchronized, preserving #3715). Every call site uses acquire() only, so dropping the ReentrantLock superclass touches no caller. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AutoClosableReentrantLock extended ReentrantLock, so every SDK object holding one allocated a ReentrantLock (and its AbstractQueuedSynchronizer) eagerly in its field initializer. A customer Perfetto trace showed ~81 such allocations on the main thread during SentryAndroid.init, many for locks that are never acquired during init. Hold the ReentrantLock internally and create it lazily on first acquire(), using an AtomicReferenceFieldUpdater CAS so creation stays atomic and Loom-friendly (no synchronized, preserving #3715). Every call site uses acquire() only, so dropping the ReentrantLock superclass touches no caller. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(core): Lazily allocate AutoClosableReentrantLock (JAVA-588) AutoClosableReentrantLock extended ReentrantLock, so every SDK object holding one allocated a ReentrantLock (and its AbstractQueuedSynchronizer) eagerly in its field initializer. A customer Perfetto trace showed ~81 such allocations on the main thread during SentryAndroid.init, many for locks that are never acquired during init. Hold the ReentrantLock internally and create it lazily on first acquire(), using an AtomicReferenceFieldUpdater CAS so creation stays atomic and Loom-friendly (no synchronized, preserving #3715). Every call site uses acquire() only, so dropping the ReentrantLock superclass touches no caller. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * changelog * perf(core): Harden lazy lock init and mark AutoClosableReentrantLock internal (JAVA-588) Replace the unreachable candidate fallback after a failed CAS with an explicit non-null check, so a broken invariant fails loudly instead of handing two threads different locks. Mark the class @ApiStatus.Internal and make the lazy-allocation test assert the lock field directly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * perf(core): Return the lock itself as the lifecycle token (JAVA-588) Every acquire() allocated a fresh lifecycle token, which is per-use garbage on every lock acquisition forever, not just at init. The token was stateless apart from its lock reference, so AutoClosableReentrantLock now implements ISentryLifecycleToken itself and acquire() returns this, making the steady-state acquire/close path allocation-free. Semantics are unchanged: try-with-resources closes once per acquire, so reentrant acquires stay balanced, and unlocking without holding the lock still throws IllegalMonitorStateException. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📜 Description
synchronizedkeyword in method signatures andsynchronized (...) {blocks have been replaced by usingReentrantLockAutoClosableReentrantLockwhich offers anacquiremethod that locks and returns anAutoClosablethat unlocks onclose. This can be used intryblocksSynchronizedCollectionandSynchronizedQueuenow expect aAutoClosableReentrantLockinstead ofObjectwhen passing a lock objectsynchronizedmethods andsynchronized (this)) now uselockstatic synchronized,synchronized (...class)andsynchronized (...getInstance)) now usestaticLockObjecttoAutoClosableReentrantLock💡 Motivation and Context
Fixes #3312
💚 How did you test it?
📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps