diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a290e2b3e9..6050ac95714 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Performance + +- Avoid a `Thread.getName()` string allocation and scan on the scope-persistence hot path by detecting the Sentry executor thread via a marker instead ([#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..d027325472f 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. Allocation-free 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();