diff --git a/CHANGELOG.md b/CHANGELOG.md index 2aee94a20b..f3bd4c18d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ### Performance - Reduce the number of SDK threads: `LifecycleWatcher` now schedules the session-end task on the shared timer executor instead of creating a dedicated `java.util.Timer` thread ([#5819](https://github.com/getsentry/sentry-java/pull/5819)) +- Avoid waiting up to `shutdownTimeoutMillis` when closing the SDK with a pending transaction timeout or session-end task ([#5851](https://github.com/getsentry/sentry-java/pull/5851)) - Speed up deserialization of arbitrary JSON objects by typing numbers without throwing exceptions ([#5783](https://github.com/getsentry/sentry-java/pull/5783)) ### Dependencies diff --git a/sentry/src/main/java/io/sentry/SentryExecutorService.java b/sentry/src/main/java/io/sentry/SentryExecutorService.java index a469dca585..fc6a7c4f81 100644 --- a/sentry/src/main/java/io/sentry/SentryExecutorService.java +++ b/sentry/src/main/java/io/sentry/SentryExecutorService.java @@ -56,6 +56,10 @@ public SentryExecutorService(final @Nullable SentryOptions options) { executorService.setRemoveOnCancelPolicy(removeOnCancelPolicy); executorService.setKeepAliveTime(keepAliveTime, keepAliveTimeUnit); executorService.allowCoreThreadTimeOut(true); + // by default shutdown() keeps queued delayed tasks, so awaitTermination blocks for the full + // shutdown timeout whenever a long timeout is still pending. Those tasks are discarded by the + // subsequent shutdownNow() anyway, so dropping them upfront only saves the wait. + executorService.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); } public SentryExecutorService() { diff --git a/sentry/src/test/java/io/sentry/SentryExecutorServiceTest.kt b/sentry/src/test/java/io/sentry/SentryExecutorServiceTest.kt index 153feecb4a..e9e68a2979 100644 --- a/sentry/src/test/java/io/sentry/SentryExecutorServiceTest.kt +++ b/sentry/src/test/java/io/sentry/SentryExecutorServiceTest.kt @@ -8,6 +8,7 @@ import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.ScheduledThreadPoolExecutor import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean +import kotlin.system.measureTimeMillis import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -110,6 +111,27 @@ class SentryExecutorServiceTest { sentryExecutor.close(15000) } + @Test + fun `SentryExecutorService discards pending delayed tasks on shutdown when requested`() { + val sentryExecutor = SentryExecutorService(null, true, 30, TimeUnit.SECONDS) + val executor = sentryExecutor.getProperty("executorService") + assertFalse(executor.executeExistingDelayedTasksAfterShutdownPolicy) + sentryExecutor.close(15000) + } + + @Test + fun `SentryExecutorService close does not wait for a pending delayed task`() { + val sentryExecutor = SentryExecutorService(null, true, 30, TimeUnit.SECONDS) + val ran = AtomicBoolean(false) + sentryExecutor.schedule({ ran.set(true) }, 30000) + + val elapsed = measureTimeMillis { sentryExecutor.close(5000) } + + assertTrue(elapsed < 5000, "close blocked for ${elapsed}ms waiting on the pending task") + assertTrue(sentryExecutor.isClosed) + assertFalse(ran.get()) + } + @Test fun `SentryExecutorService isClosed returns true if executor is shutdown`() { val executor = mock()