Skip to content

Commit 9140184

Browse files
runningcodeclaude
andcommitted
perf(core): Detect Sentry executor thread without name scan (JAVA-607)
PersistingScopeObserver.serializeToDisk gated the on-executor fast path by scanning Thread.currentThread().getName(), which allocates a String and scans it on every scope mutation (breadcrumb, tag, trace, ...). Tag the executor threads with a marker Thread subclass and expose an allocation-free SentryExecutorService.isSentryExecutorThread() check instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent aab952b commit 9140184

3 files changed

Lines changed: 20 additions & 2 deletions

File tree

sentry/api/sentry.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3181,6 +3181,7 @@ public final class io/sentry/SentryExecutorService : io/sentry/ISentryExecutorSe
31813181
public fun <init> (Lio/sentry/SentryOptions;)V
31823182
public fun close (J)V
31833183
public fun isClosed ()Z
3184+
public static fun isSentryExecutorThread ()Z
31843185
public fun prewarm ()V
31853186
public fun schedule (Ljava/lang/Runnable;J)Ljava/util/concurrent/Future;
31863187
public fun submit (Ljava/lang/Runnable;)Ljava/util/concurrent/Future;

sentry/src/main/java/io/sentry/SentryExecutorService.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,17 +149,33 @@ public void prewarm() {
149149
}
150150
}
151151

152+
/**
153+
* Whether the calling thread is one of the Sentry executor threads. Allocation-free replacement
154+
* for scanning {@code Thread.currentThread().getName()}, which is on hot paths (e.g. scope
155+
* persistence on every scope mutation).
156+
*/
157+
public static boolean isSentryExecutorThread() {
158+
return Thread.currentThread() instanceof SentryExecutorServiceThread;
159+
}
160+
152161
private static final class SentryExecutorServiceThreadFactory implements ThreadFactory {
153162
private int cnt;
154163

155164
@Override
156165
public @NotNull Thread newThread(final @NotNull Runnable r) {
157-
final Thread ret = new Thread(r, "SentryExecutorServiceThreadFactory-" + cnt++);
166+
final Thread ret =
167+
new SentryExecutorServiceThread(r, "SentryExecutorServiceThreadFactory-" + cnt++);
158168
ret.setDaemon(true);
159169
return ret;
160170
}
161171
}
162172

173+
private static final class SentryExecutorServiceThread extends Thread {
174+
SentryExecutorServiceThread(final @NotNull Runnable r, final @NotNull String name) {
175+
super(r, name);
176+
}
177+
}
178+
163179
private static final class CancelledFuture<T> implements Future<T> {
164180
@Override
165181
public boolean cancel(final boolean mayInterruptIfRunning) {

sentry/src/main/java/io/sentry/cache/PersistingScopeObserver.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import io.sentry.Breadcrumb;
88
import io.sentry.IScope;
99
import io.sentry.ScopeObserverAdapter;
10+
import io.sentry.SentryExecutorService;
1011
import io.sentry.SentryLevel;
1112
import io.sentry.SentryOptions;
1213
import io.sentry.SpanContext;
@@ -229,7 +230,7 @@ private void serializeToDisk(final @NotNull Runnable task) {
229230
if (!options.isEnableScopePersistence()) {
230231
return;
231232
}
232-
if (Thread.currentThread().getName().contains("SentryExecutor")) {
233+
if (SentryExecutorService.isSentryExecutorThread()) {
233234
// we're already on the sentry executor thread, so we can just execute it directly
234235
try {
235236
task.run();

0 commit comments

Comments
 (0)