Problem
PersistingScopeObserver.serializeToDisk decides whether it is already on the Sentry executor thread by scanning the thread name:
if (Thread.currentThread().getName().contains("SentryExecutor")) {
task.run();
return;
}
Thread.getName() allocates a fresh String and .contains() scans it — on every scope mutation (breadcrumb, tag, trace, user, etc.). In the trace this path runs 3,000+ times, including on the main thread as the synchronous part of addBreadcrumb.
Evidence
From on-device method trace analysis (median.perfetto-trace): PersistingScopeObserver#addBreadcrumb sync part was ~10ms main-thread self-time / 65 calls, and the same serializeToDisk gate runs for all ~3,130 scope stores. The string allocation + scan is pure overhead on a very hot path.
Fix
The executor already uses a dedicated SentryExecutorServiceThreadFactory (SentryExecutorService). Tag those threads (a marker Thread subclass or a ThreadLocal<Boolean> set in newThread) and replace the name scan with an allocation-free identity/boolean check:
if (SentryExecutorThreadMarker.isCurrentThreadSentryExecutor()) {
task.run();
return;
}
Keep the marker package-private/internal so there is no public API change.
Acceptance
No String allocation on the serializeToDisk thread check; the on-executor fast-path still short-circuits correctly; no .api change.
Problem
PersistingScopeObserver.serializeToDiskdecides whether it is already on the Sentry executor thread by scanning the thread name:Thread.getName()allocates a freshStringand.contains()scans it — on every scope mutation (breadcrumb, tag, trace, user, etc.). In the trace this path runs 3,000+ times, including on the main thread as the synchronous part ofaddBreadcrumb.Evidence
From on-device method trace analysis (
median.perfetto-trace):PersistingScopeObserver#addBreadcrumbsync part was ~10ms main-thread self-time / 65 calls, and the sameserializeToDiskgate runs for all ~3,130 scope stores. The string allocation + scan is pure overhead on a very hot path.Fix
The executor already uses a dedicated
SentryExecutorServiceThreadFactory(SentryExecutorService). Tag those threads (a markerThreadsubclass or aThreadLocal<Boolean>set innewThread) and replace the name scan with an allocation-free identity/boolean check:Keep the marker package-private/internal so there is no public API change.
Acceptance
No
Stringallocation on theserializeToDiskthread check; the on-executor fast-path still short-circuits correctly; no.apichange.