From 57dc743dcae9b1b9dd156b2d01588514864fb564 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 | 6 ++++++ sentry/api/sentry.api | 1 + .../java/io/sentry/SentryExecutorService.java | 18 +++++++++++++++++- .../sentry/cache/PersistingScopeObserver.java | 3 ++- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a290e2b3e9..91aae4f53ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Performance + +- 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 ### Behavioral Changes diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 383ea92b116..32e9438cf4f 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -3181,6 +3181,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 c9356579c9c..9f406f6aa30 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 try { task.run(); From 7c874b7c545b6f09cf998ecf744ba18279828eb6 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;