Skip to content

Commit afd4170

Browse files
Removed SentryExecutorService limit (#4846)
* removed SentryExecutorService max queue limit * updated changelog * Format code * format code * readded executor queue limit for submit() submit() now tries to purge if it reaches the limit * updated changelog * Format code * added purge test * Format code * added purge test --------- Co-authored-by: Sentry Github Bot <[email protected]>
1 parent 6405ec5 commit afd4170

File tree

3 files changed

+38
-32
lines changed

3 files changed

+38
-32
lines changed

CHANGELOG.md

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

1919
### Fixes
2020

21+
- Removed SentryExecutorService limit for delayed scheduled tasks ([#4846](https://github.com/getsentry/sentry-java/pull/4846))
2122
- [ANR] Removed AndroidTransactionProfiler lock ([#4817](https://github.com/getsentry/sentry-java/pull/4817))
2223
- Avoid ExecutorService for DefaultCompositePerformanceCollector timeout ([#4841](https://github.com/getsentry/sentry-java/pull/4841))
2324
- This avoids infinite data collection for never stopped transactions, leading to OOMs

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

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,19 @@ public SentryExecutorService() {
5353
this(new ScheduledThreadPoolExecutor(1, new SentryExecutorServiceThreadFactory()), null);
5454
}
5555

56+
private boolean isQueueAvailable() {
57+
// If limit is reached, purge cancelled tasks from the queue
58+
if (executorService.getQueue().size() >= MAX_QUEUE_SIZE) {
59+
executorService.purge();
60+
}
61+
// Check limit again after purge
62+
return executorService.getQueue().size() < MAX_QUEUE_SIZE;
63+
}
64+
5665
@Override
5766
public @NotNull Future<?> submit(final @NotNull Runnable runnable)
5867
throws RejectedExecutionException {
59-
if (executorService.getQueue().size() < MAX_QUEUE_SIZE) {
68+
if (isQueueAvailable()) {
6069
return executorService.submit(runnable);
6170
}
6271
if (options != null) {
@@ -70,7 +79,7 @@ public SentryExecutorService() {
7079
@Override
7180
public @NotNull <T> Future<T> submit(final @NotNull Callable<T> callable)
7281
throws RejectedExecutionException {
73-
if (executorService.getQueue().size() < MAX_QUEUE_SIZE) {
82+
if (isQueueAvailable()) {
7483
return executorService.submit(callable);
7584
}
7685
if (options != null) {
@@ -84,15 +93,7 @@ public SentryExecutorService() {
8493
@Override
8594
public @NotNull Future<?> schedule(final @NotNull Runnable runnable, final long delayMillis)
8695
throws RejectedExecutionException {
87-
if (executorService.getQueue().size() < MAX_QUEUE_SIZE) {
88-
return executorService.schedule(runnable, delayMillis, TimeUnit.MILLISECONDS);
89-
}
90-
if (options != null) {
91-
options
92-
.getLogger()
93-
.log(SentryLevel.WARNING, "Task " + runnable + " rejected from " + executorService);
94-
}
95-
return new CancelledFuture<>();
96+
return executorService.schedule(runnable, delayMillis, TimeUnit.MILLISECONDS);
9697
}
9798

9899
@Override

sentry/src/test/java/io/sentry/SentryExecutorServiceTest.kt

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import java.util.concurrent.Callable
66
import java.util.concurrent.CancellationException
77
import java.util.concurrent.LinkedBlockingQueue
88
import java.util.concurrent.ScheduledThreadPoolExecutor
9+
import java.util.concurrent.TimeUnit
910
import java.util.concurrent.atomic.AtomicBoolean
1011
import kotlin.test.Test
1112
import kotlin.test.assertEquals
@@ -177,27 +178,6 @@ class SentryExecutorServiceTest {
177178
verify(executor).submit(any<Callable<String>>())
178179
}
179180

180-
@Test
181-
fun `SentryExecutorService schedule returns cancelled future when queue size exceeds limit`() {
182-
val queue = mock<BlockingQueue<Runnable>>()
183-
whenever(queue.size).thenReturn(272) // Above MAX_QUEUE_SIZE (271)
184-
185-
val executor = mock<ScheduledThreadPoolExecutor> { on { getQueue() } doReturn queue }
186-
187-
val options = mock<SentryOptions>()
188-
val logger = mock<ILogger>()
189-
whenever(options.logger).thenReturn(logger)
190-
191-
val sentryExecutor = SentryExecutorService(executor, options)
192-
val future = sentryExecutor.schedule({}, 1000L)
193-
194-
assertTrue(future.isCancelled)
195-
assertTrue(future.isDone)
196-
assertFailsWith<CancellationException> { future.get() }
197-
verify(executor, never()).schedule(any<Runnable>(), any(), any())
198-
verify(logger).log(any<SentryLevel>(), any<String>())
199-
}
200-
201181
@Test
202182
fun `SentryExecutorService schedule accepts when queue size is within limit`() {
203183
val queue = mock<BlockingQueue<Runnable>>()
@@ -225,4 +205,28 @@ class SentryExecutorServiceTest {
225205
// the queue should be empty
226206
assertEquals(0, executor.queue.size)
227207
}
208+
209+
@Test
210+
fun `SentryExecutorService schedules any number of job`() {
211+
val executor = ScheduledThreadPoolExecutor(1)
212+
val sentryExecutor = SentryExecutorService(executor, null)
213+
// Post 1k jobs after 1 day, to test they are all accepted
214+
repeat(1000) { sentryExecutor.schedule({}, TimeUnit.DAYS.toMillis(1)) }
215+
assertEquals(1000, executor.queue.size)
216+
}
217+
218+
@Test
219+
fun `SentryExecutorService purges cancelled jobs when limit is reached`() {
220+
val executor = ScheduledThreadPoolExecutor(1)
221+
val sentryExecutor = SentryExecutorService(executor, null)
222+
// Post 1k jobs after 1 day, to test they are all accepted
223+
repeat(1000) {
224+
val future = sentryExecutor.schedule({}, TimeUnit.DAYS.toMillis(1))
225+
future.cancel(true)
226+
}
227+
assertEquals(1000, executor.queue.size)
228+
// Submit should purge cancelled scheduled jobs
229+
sentryExecutor.submit {}
230+
assertEquals(1, executor.queue.size)
231+
}
228232
}

0 commit comments

Comments
 (0)