Skip to content

Commit 15f9b19

Browse files
committed
Merge branch '08-04-attempt_to_fix_kotlin_2.2_issue' into 08-07-fix_tests_for_spring_7_and_spring_boot_4
2 parents b76a0ce + f90693a commit 15f9b19

32 files changed

Lines changed: 483 additions & 107 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- Session Replay: Use main thread looper to schedule replay capture ([#4542](https://github.com/getsentry/sentry-java/pull/4542))
88
- Use single `LifecycleObserver` and multi-cast it to the integrations interested in lifecycle states ([#4567](https://github.com/getsentry/sentry-java/pull/4567))
9+
- Prewarm `SentryExecutorService` for better performance at runtime ([#4606](https://github.com/getsentry/sentry-java/pull/4606))
910

1011
### Fixes
1112

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ plugins {
2727
// dokka is required by gradle-maven-publish-plugin.
2828
alias(libs.plugins.dokka) apply false
2929
alias(libs.plugins.dokka.javadoc) apply false
30-
alias(libs.plugins.compose.compiler) apply false
30+
alias(libs.plugins.kotlin.compose) apply false
3131
alias(libs.plugins.errorprone) apply false
3232
alias(libs.plugins.gradle.versions) apply false
3333
alias(libs.plugins.spring.dependency.management) apply false

gradle/libs.versions.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ feign = "11.6"
1111
jacoco = "0.8.7"
1212
jackson = "2.18.3"
1313
jetbrainsCompose = "1.6.11"
14-
kotlin = "1.9.24"
14+
kotlin = "2.2.0"
1515
kotlinSpring7 = "2.2.0"
1616
kotlin-compatible-version = "1.9"
1717
ktorClient = "3.0.0"
@@ -50,7 +50,6 @@ buildconfig = { id = "com.github.gmazzo.buildconfig", version = "5.6.5" }
5050
dokka = { id = "org.jetbrains.dokka", version = "2.0.0" }
5151
dokka-javadoc = { id = "org.jetbrains.dokka-javadoc", version = "2.0.0" }
5252
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version = "0.13.0" }
53-
compose-compiler = { id = "org.jetbrains.compose", version.ref = "jetbrainsCompose" }
5453
errorprone = { id = "net.ltgt.errorprone", version = "3.0.1" }
5554
gradle-versions = { id = "com.github.ben-manes.versions", version = "0.42.0" }
5655
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }

sentry-android-core/src/main/java/io/sentry/android/core/ContextUtils.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import android.content.pm.PackageInfo;
1616
import android.content.pm.PackageManager;
1717
import android.os.Build;
18+
import android.os.Handler;
1819
import android.util.DisplayMetrics;
1920
import io.sentry.ILogger;
2021
import io.sentry.SentryLevel;
@@ -455,8 +456,10 @@ public static boolean appIsLibraryForComposePreview(final @NotNull Context conte
455456
final @NotNull Context context,
456457
final @NotNull SentryOptions options,
457458
final @Nullable BroadcastReceiver receiver,
458-
final @NotNull IntentFilter filter) {
459-
return registerReceiver(context, new BuildInfoProvider(options.getLogger()), receiver, filter);
459+
final @NotNull IntentFilter filter,
460+
final @Nullable Handler handler) {
461+
return registerReceiver(
462+
context, new BuildInfoProvider(options.getLogger()), receiver, filter, handler);
460463
}
461464

462465
/** Register an exported BroadcastReceiver, independently from platform version. */
@@ -465,15 +468,17 @@ public static boolean appIsLibraryForComposePreview(final @NotNull Context conte
465468
final @NotNull Context context,
466469
final @NotNull BuildInfoProvider buildInfoProvider,
467470
final @Nullable BroadcastReceiver receiver,
468-
final @NotNull IntentFilter filter) {
471+
final @NotNull IntentFilter filter,
472+
final @Nullable Handler handler) {
469473
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.TIRAMISU) {
470474
// From https://developer.android.com/guide/components/broadcasts#context-registered-receivers
471475
// If this receiver is listening for broadcasts sent from the system or from other apps, even
472476
// other apps that you own—use the RECEIVER_EXPORTED flag. If instead this receiver is
473477
// listening only for broadcasts sent by your app, use the RECEIVER_NOT_EXPORTED flag.
474-
return context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED);
478+
return context.registerReceiver(
479+
receiver, filter, null, handler, Context.RECEIVER_NOT_EXPORTED);
475480
} else {
476-
return context.registerReceiver(receiver, filter);
481+
return context.registerReceiver(receiver, filter, null, handler);
477482
}
478483
}
479484

sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ private Date getBootTime() {
275275
@Nullable
276276
private Intent getBatteryIntent() {
277277
return ContextUtils.registerReceiver(
278-
context, buildInfoProvider, null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
278+
context, buildInfoProvider, null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED), null);
279279
}
280280

281281
/**

sentry-android-core/src/main/java/io/sentry/android/core/LifecycleWatcher.java

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import io.sentry.transport.CurrentDateProvider;
99
import io.sentry.transport.ICurrentDateProvider;
1010
import io.sentry.util.AutoClosableReentrantLock;
11+
import io.sentry.util.LazyEvaluator;
1112
import java.util.Timer;
1213
import java.util.TimerTask;
1314
import java.util.concurrent.atomic.AtomicLong;
@@ -22,7 +23,7 @@ final class LifecycleWatcher implements AppState.AppStateListener {
2223
private final long sessionIntervalMillis;
2324

2425
private @Nullable TimerTask timerTask;
25-
private final @NotNull Timer timer = new Timer(true);
26+
private final @NotNull LazyEvaluator<Timer> timer = new LazyEvaluator<>(() -> new Timer(true));
2627
private final @NotNull AutoClosableReentrantLock timerLock = new AutoClosableReentrantLock();
2728
private final @NotNull IScopes scopes;
2829
private final boolean enableSessionTracking;
@@ -105,21 +106,19 @@ public void onBackground() {
105106
private void scheduleEndSession() {
106107
try (final @NotNull ISentryLifecycleToken ignored = timerLock.acquire()) {
107108
cancelTask();
108-
if (timer != null) {
109-
timerTask =
110-
new TimerTask() {
111-
@Override
112-
public void run() {
113-
if (enableSessionTracking) {
114-
scopes.endSession();
115-
}
116-
scopes.getOptions().getReplayController().stop();
117-
scopes.getOptions().getContinuousProfiler().close(false);
109+
timerTask =
110+
new TimerTask() {
111+
@Override
112+
public void run() {
113+
if (enableSessionTracking) {
114+
scopes.endSession();
118115
}
119-
};
116+
scopes.getOptions().getReplayController().stop();
117+
scopes.getOptions().getContinuousProfiler().close(false);
118+
}
119+
};
120120

121-
timer.schedule(timerTask, sessionIntervalMillis);
122-
}
121+
timer.getValue().schedule(timerTask, sessionIntervalMillis);
123122
}
124123
}
125124

@@ -152,6 +151,6 @@ TimerTask getTimerTask() {
152151
@TestOnly
153152
@NotNull
154153
Timer getTimer() {
155-
return timer;
154+
return timer.getValue();
156155
}
157156
}

sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
import android.content.Intent;
2626
import android.content.IntentFilter;
2727
import android.os.Bundle;
28+
import android.os.Handler;
29+
import android.os.HandlerThread;
30+
import android.os.Process;
2831
import io.sentry.Breadcrumb;
2932
import io.sentry.Hint;
3033
import io.sentry.IScopes;
@@ -63,6 +66,7 @@ public final class SystemEventsBreadcrumbsIntegration
6366
private volatile boolean isClosed = false;
6467
private volatile boolean isStopped = false;
6568
private volatile IntentFilter filter = null;
69+
private volatile HandlerThread handlerThread = null;
6670
private final @NotNull AtomicBoolean isReceiverRegistered = new AtomicBoolean(false);
6771
private final @NotNull AutoClosableReentrantLock receiverLock = new AutoClosableReentrantLock();
6872
// Track previous battery state to avoid duplicate breadcrumbs when values haven't changed
@@ -138,10 +142,19 @@ private void registerReceiver(
138142
filter.addAction(item);
139143
}
140144
}
145+
if (handlerThread == null) {
146+
handlerThread =
147+
new HandlerThread(
148+
"SystemEventsReceiver", Process.THREAD_PRIORITY_BACKGROUND);
149+
handlerThread.start();
150+
}
141151
try {
142152
// registerReceiver can throw SecurityException but it's not documented in the
143153
// official docs
144-
ContextUtils.registerReceiver(context, options, receiver, filter);
154+
155+
// onReceive will be called on this handler thread
156+
final @NotNull Handler handler = new Handler(handlerThread.getLooper());
157+
ContextUtils.registerReceiver(context, options, receiver, filter, handler);
145158
if (!isReceiverRegistered.getAndSet(true)) {
146159
options
147160
.getLogger()
@@ -195,6 +208,10 @@ public void close() throws IOException {
195208
try (final @NotNull ISentryLifecycleToken ignored = receiverLock.acquire()) {
196209
isClosed = true;
197210
filter = null;
211+
if (handlerThread != null) {
212+
handlerThread.quit();
213+
}
214+
handlerThread = null;
198215
}
199216

200217
AppState.getInstance().removeAppStateListener(this);
@@ -293,25 +310,15 @@ public void onReceive(final Context context, final @NotNull Intent intent) {
293310

294311
final BatteryState state = batteryState;
295312
final long now = System.currentTimeMillis();
296-
try {
297-
options
298-
.getExecutorService()
299-
.submit(
300-
() -> {
301-
final Breadcrumb breadcrumb = createBreadcrumb(now, intent, action, state);
302-
final Hint hint = new Hint();
303-
hint.set(ANDROID_INTENT, intent);
304-
scopes.addBreadcrumb(breadcrumb, hint);
305-
});
306-
} catch (Throwable t) {
307-
// ignored
308-
}
313+
final Breadcrumb breadcrumb = createBreadcrumb(now, intent, action, state);
314+
final Hint hint = new Hint();
315+
hint.set(ANDROID_INTENT, intent);
316+
scopes.addBreadcrumb(breadcrumb, hint);
309317
}
310318

311319
// in theory this should be ThreadLocal, but we won't have more than 1 thread accessing it,
312320
// so we save some memory here and CPU cycles. 64 is because all intent actions we subscribe for
313321
// are less than 64 chars. We also don't care about encoding as those are always UTF.
314-
// TODO: _MULTI_THREADED_EXECUTOR_
315322
private final char[] buf = new char[64];
316323

317324
@TestOnly
@@ -365,8 +372,8 @@ String getStringAfterDotFast(final @Nullable String str) {
365372
}
366373
} else {
367374
final Bundle extras = intent.getExtras();
368-
final Map<String, String> newExtras = new HashMap<>();
369375
if (extras != null && !extras.isEmpty()) {
376+
final Map<String, String> newExtras = new HashMap<>(extras.size());
370377
for (String item : extras.keySet()) {
371378
try {
372379
@SuppressWarnings("deprecation")

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

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

8282
override fun isClosed() = false
83+
84+
override fun prewarm() = Unit
8385
}
8486

8587
val options =

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

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

9191
override fun isClosed() = false
92+
93+
override fun prewarm() = Unit
9294
}
9395

9496
val options =

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import kotlin.test.assertTrue
3030
import org.junit.runner.RunWith
3131
import org.mockito.kotlin.any
3232
import org.mockito.kotlin.eq
33+
import org.mockito.kotlin.isNull
3334
import org.mockito.kotlin.mock
3435
import org.mockito.kotlin.spy
3536
import org.mockito.kotlin.verify
@@ -221,8 +222,8 @@ class ContextUtilsTest {
221222
val filter = mock<IntentFilter>()
222223
val context = mock<Context>()
223224
whenever(buildInfo.sdkInfoVersion).thenReturn(Build.VERSION_CODES.S)
224-
ContextUtils.registerReceiver(context, buildInfo, receiver, filter)
225-
verify(context).registerReceiver(eq(receiver), eq(filter))
225+
ContextUtils.registerReceiver(context, buildInfo, receiver, filter, null)
226+
verify(context).registerReceiver(eq(receiver), eq(filter), isNull(), isNull())
226227
}
227228

228229
@Test
@@ -232,8 +233,15 @@ class ContextUtilsTest {
232233
val filter = mock<IntentFilter>()
233234
val context = mock<Context>()
234235
whenever(buildInfo.sdkInfoVersion).thenReturn(Build.VERSION_CODES.TIRAMISU)
235-
ContextUtils.registerReceiver(context, buildInfo, receiver, filter)
236-
verify(context).registerReceiver(eq(receiver), eq(filter), eq(Context.RECEIVER_NOT_EXPORTED))
236+
ContextUtils.registerReceiver(context, buildInfo, receiver, filter, null)
237+
verify(context)
238+
.registerReceiver(
239+
eq(receiver),
240+
eq(filter),
241+
isNull(),
242+
isNull(),
243+
eq(Context.RECEIVER_NOT_EXPORTED),
244+
)
237245
}
238246

239247
@Test

0 commit comments

Comments
 (0)