From d1e2fbc04c7cda2fdc254e45d06e35b5a9317450 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 2 Jul 2026 18:53:15 +0200 Subject: [PATCH 1/2] 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 --- CHANGELOG.md | 1 + sentry/api/sentry.api | 1 + .../java/io/sentry/SentryExecutorService.java | 18 +++++++++++++++++- .../sentry/cache/PersistingScopeObserver.java | 3 ++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81a7975bfb4..3505a359c36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ ### Performance - Skip `Hint` allocation in `Scope.addBreadcrumb` when no `beforeBreadcrumb` callback is set ([#5689](https://github.com/getsentry/sentry-java/pull/5689)) +- Speed up scope persistence by detecting the Sentry executor thread via a marker instead of a `Thread.getName()` name scan on every scope mutation ([#5691](https://github.com/getsentry/sentry-java/pull/5691)) ## 8.47.0 diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index d62ecf41193..5b37d89e848 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -3182,6 +3182,7 @@ public final class io/sentry/SentryExecutorService : io/sentry/ISentryExecutorSe public fun (Lio/sentry/SentryOptions;)V public fun close (J)V public fun isClosed ()Z + public static fun isSentryExecutorThread ()Z public fun prewarm ()V public fun schedule (Ljava/lang/Runnable;J)Ljava/util/concurrent/Future; public fun submit (Ljava/lang/Runnable;)Ljava/util/concurrent/Future; diff --git a/sentry/src/main/java/io/sentry/SentryExecutorService.java b/sentry/src/main/java/io/sentry/SentryExecutorService.java index adb50b232e9..54efe3f8298 100644 --- a/sentry/src/main/java/io/sentry/SentryExecutorService.java +++ b/sentry/src/main/java/io/sentry/SentryExecutorService.java @@ -149,17 +149,33 @@ public void prewarm() { } } + /** + * Whether the calling thread is one of the Sentry executor threads. Cheap replacement for scanning + * {@code Thread.currentThread().getName()}, which is on hot paths (e.g. scope persistence on every + * scope mutation). + */ + public static boolean isSentryExecutorThread() { + return Thread.currentThread() instanceof SentryExecutorServiceThread; + } + private static final class SentryExecutorServiceThreadFactory implements ThreadFactory { private int cnt; @Override public @NotNull Thread newThread(final @NotNull Runnable r) { - final Thread ret = new Thread(r, "SentryExecutorServiceThreadFactory-" + cnt++); + final Thread ret = + new SentryExecutorServiceThread(r, "SentryExecutorServiceThreadFactory-" + cnt++); ret.setDaemon(true); return ret; } } + private static final class SentryExecutorServiceThread extends Thread { + SentryExecutorServiceThread(final @NotNull Runnable r, final @NotNull String name) { + super(r, name); + } + } + private static final class CancelledFuture implements Future { @Override public boolean cancel(final boolean mayInterruptIfRunning) { diff --git a/sentry/src/main/java/io/sentry/cache/PersistingScopeObserver.java b/sentry/src/main/java/io/sentry/cache/PersistingScopeObserver.java index edfcc71b9a6..420d0d31e22 100644 --- a/sentry/src/main/java/io/sentry/cache/PersistingScopeObserver.java +++ b/sentry/src/main/java/io/sentry/cache/PersistingScopeObserver.java @@ -7,6 +7,7 @@ import io.sentry.Breadcrumb; import io.sentry.IScope; import io.sentry.ScopeObserverAdapter; +import io.sentry.SentryExecutorService; import io.sentry.SentryLevel; import io.sentry.SentryOptions; import io.sentry.SpanContext; @@ -229,7 +230,7 @@ private void serializeToDisk(final @NotNull Runnable task) { if (!options.isEnableScopePersistence()) { return; } - if (Thread.currentThread().getName().contains("SentryExecutor")) { + if (SentryExecutorService.isSentryExecutorThread()) { // we're already on the sentry executor thread, so we can just execute it directly runSafely(task); return; From 8b38b7c89247eec6b16db93fcd24dc25a5093e52 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Fri, 3 Jul 2026 09:37:40 +0000 Subject: [PATCH 2/2] Format code --- sentry/src/main/java/io/sentry/SentryExecutorService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sentry/src/main/java/io/sentry/SentryExecutorService.java b/sentry/src/main/java/io/sentry/SentryExecutorService.java index 54efe3f8298..3e24a1a5992 100644 --- a/sentry/src/main/java/io/sentry/SentryExecutorService.java +++ b/sentry/src/main/java/io/sentry/SentryExecutorService.java @@ -150,9 +150,9 @@ public void prewarm() { } /** - * Whether the calling thread is one of the Sentry executor threads. Cheap replacement for scanning - * {@code Thread.currentThread().getName()}, which is on hot paths (e.g. scope persistence on every - * scope mutation). + * Whether the calling thread is one of the Sentry executor threads. Cheap replacement for + * scanning {@code Thread.currentThread().getName()}, which is on hot paths (e.g. scope + * persistence on every scope mutation). */ public static boolean isSentryExecutorThread() { return Thread.currentThread() instanceof SentryExecutorServiceThread;