Skip to content

Commit e8b898b

Browse files
runningcodeclaude
andcommitted
perf(core): Remove executor prewarm
SentryExecutorService is single-threaded, and prewarm() is submitted ahead of loadLazyFields() during init, so its 40-task schedule/cancel/purge loop cannot reduce first-task latency — it can only delay it. The thread creation and executor class loading it aimed to warm are paid identically by the first real task (loadLazyFields), which is submitted unconditionally right after, so prewarm warms nothing that would not already be warmed. On-device A/B benchmarks on a Galaxy A55 (Android 16) show no measurable first-useful-task speedup from prewarm and ~20us of extra background-thread work. Remove prewarm() from ISentryExecutorService and its implementations and from both init call sites. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 87f4f63 commit e8b898b

12 files changed

Lines changed: 2 additions & 80 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
- Skip `Hint` allocation in `Scope.addBreadcrumb` when no `beforeBreadcrumb` callback is set ([#5689](https://github.com/getsentry/sentry-java/pull/5689))
1919
- 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))
20+
- Remove executor prewarm during SDK init ([#5681](https://github.com/getsentry/sentry-java/pull/5681))
21+
- The single-threaded `SentryExecutorService` queued the prewarm work ahead of the first useful task, so it could only delay init work, never speed it up; the thread and class loading it warmed are paid identically by the first real task submitted right after.
2022

2123
## 8.47.0
2224

sentry-android-core/src/test/java/io/sentry/android/core/AndroidProfilerTest.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,6 @@ class AndroidProfilerTest {
8181
override fun close(timeoutMillis: Long) {}
8282

8383
override fun isClosed() = false
84-
85-
override fun prewarm() = Unit
8684
}
8785

8886
val options =

sentry-android-core/src/test/java/io/sentry/android/core/AndroidTransactionProfilerTest.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,6 @@ class AndroidTransactionProfilerTest {
8989
override fun close(timeoutMillis: Long) {}
9090

9191
override fun isClosed() = false
92-
93-
override fun prewarm() = Unit
9492
}
9593

9694
val options =

sentry-test-support/src/main/kotlin/io/sentry/test/Mocks.kt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ class ImmediateExecutorService : ISentryExecutorService {
3535
override fun close(timeoutMillis: Long) {}
3636

3737
override fun isClosed(): Boolean = false
38-
39-
override fun prewarm() = Unit
4038
}
4139

4240
class DeferredExecutorService : ISentryExecutorService {
@@ -74,8 +72,6 @@ class DeferredExecutorService : ISentryExecutorService {
7472

7573
override fun isClosed(): Boolean = false
7674

77-
override fun prewarm() = Unit
78-
7975
fun hasScheduledRunnables(): Boolean = scheduledRunnables.isNotEmpty()
8076
}
8177

@@ -90,8 +86,6 @@ class NonOverridableNoOpSentryExecutorService : ISentryExecutorService {
9086
override fun close(timeoutMillis: Long) {}
9187

9288
override fun isClosed(): Boolean = false
93-
94-
override fun prewarm() = Unit
9589
}
9690

9791
fun createSentryClientMock(enabled: Boolean = true) =

sentry/api/sentry.api

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,7 +1134,6 @@ public abstract interface class io/sentry/ISentryClient {
11341134
public abstract interface class io/sentry/ISentryExecutorService {
11351135
public abstract fun close (J)V
11361136
public abstract fun isClosed ()Z
1137-
public abstract fun prewarm ()V
11381137
public abstract fun schedule (Ljava/lang/Runnable;J)Ljava/util/concurrent/Future;
11391138
public abstract fun submit (Ljava/lang/Runnable;)Ljava/util/concurrent/Future;
11401139
public abstract fun submit (Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future;
@@ -1880,7 +1879,6 @@ public final class io/sentry/NoOpSentryExecutorService : io/sentry/ISentryExecut
18801879
public fun close (J)V
18811880
public static fun getInstance ()Lio/sentry/ISentryExecutorService;
18821881
public fun isClosed ()Z
1883-
public fun prewarm ()V
18841882
public fun schedule (Ljava/lang/Runnable;J)Ljava/util/concurrent/Future;
18851883
public fun submit (Ljava/lang/Runnable;)Ljava/util/concurrent/Future;
18861884
public fun submit (Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future;
@@ -3183,7 +3181,6 @@ public final class io/sentry/SentryExecutorService : io/sentry/ISentryExecutorSe
31833181
public fun close (J)V
31843182
public fun isClosed ()Z
31853183
public static fun isSentryExecutorThread ()Z
3186-
public fun prewarm ()V
31873184
public fun schedule (Ljava/lang/Runnable;J)Ljava/util/concurrent/Future;
31883185
public fun submit (Ljava/lang/Runnable;)Ljava/util/concurrent/Future;
31893186
public fun submit (Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future;

sentry/src/main/java/io/sentry/ISentryExecutorService.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,4 @@ Future<?> schedule(final @NotNull Runnable runnable, final long delayMillis)
4545
* @return If the executorService was previously closed
4646
*/
4747
boolean isClosed();
48-
49-
/**
50-
* Pre-warms the executor service by increasing the initial queue capacity. SHOULD be called
51-
* directly after instantiating this executor service.
52-
*/
53-
void prewarm();
5448
}

sentry/src/main/java/io/sentry/NoOpSentryExecutorService.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,4 @@ public void close(long timeoutMillis) {}
3838
public boolean isClosed() {
3939
return false;
4040
}
41-
42-
@Override
43-
public void prewarm() {}
4441
}

sentry/src/main/java/io/sentry/Sentry.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,6 @@ private static void init(final @NotNull SentryOptions options, final boolean glo
351351
// to set a new one
352352
if (options.getExecutorService().isClosed()) {
353353
options.setExecutorService(new SentryExecutorService(options));
354-
options.getExecutorService().prewarm();
355354
}
356355

357356
// load lazy fields of the options in a separate thread

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

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@
1616
@ApiStatus.Internal
1717
public final class SentryExecutorService implements ISentryExecutorService {
1818

19-
/**
20-
* ScheduledThreadPoolExecutor grows work queue by 50% each time. With the initial capacity of 16
21-
* it will have to resize 4 times to reach 40, which is a decent middle-ground for prewarming.
22-
* This will prevent from growing in unexpected areas of the SDK.
23-
*/
24-
private static final int INITIAL_QUEUE_SIZE = 40;
25-
2619
/**
2720
* By default, the work queue is unbounded so it can grow as much as the memory allows. We want to
2821
* limit it by 271 which would be x8 times growth from the default initial capacity.
@@ -32,9 +25,6 @@ public final class SentryExecutorService implements ISentryExecutorService {
3225
private final @NotNull ScheduledThreadPoolExecutor executorService;
3326
private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock();
3427

35-
@SuppressWarnings("UnnecessaryLambda")
36-
private final @NotNull Runnable dummyRunnable = () -> {};
37-
3828
private final @Nullable SentryOptions options;
3929

4030
@TestOnly
@@ -120,35 +110,6 @@ public boolean isClosed() {
120110
}
121111
}
122112

123-
@SuppressWarnings({"FutureReturnValueIgnored"})
124-
@Override
125-
public void prewarm() {
126-
try {
127-
executorService.submit(
128-
() -> {
129-
try {
130-
// schedule a bunch of dummy runnables in the future that will never execute to
131-
// trigger
132-
// queue growth and then purge the queue
133-
for (int i = 0; i < INITIAL_QUEUE_SIZE; i++) {
134-
final Future<?> future =
135-
executorService.schedule(dummyRunnable, 365L, TimeUnit.DAYS);
136-
future.cancel(true);
137-
}
138-
executorService.purge();
139-
} catch (RejectedExecutionException ignored) {
140-
// ignore
141-
}
142-
});
143-
} catch (RejectedExecutionException e) {
144-
if (options != null) {
145-
options
146-
.getLogger()
147-
.log(SentryLevel.WARNING, "Prewarm task rejected from " + executorService, e);
148-
}
149-
}
150-
}
151-
152113
/**
153114
* Whether the calling thread is one of the Sentry executor threads. Cheap replacement for
154115
* scanning {@code Thread.currentThread().getName()}, which is on hot paths (e.g. scope

sentry/src/main/java/io/sentry/SentryOptions.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,6 @@ public void activate() {
678678
// SentryExecutorService should be initialized before any
679679
// SendCachedEventFireAndForgetIntegration
680680
executorService = new SentryExecutorService(this);
681-
executorService.prewarm();
682681
}
683682

684683
// SpotlightIntegration is loaded via reflection to allow the sentry-spotlight module

0 commit comments

Comments
 (0)