-
-
Notifications
You must be signed in to change notification settings - Fork 469
Expand file tree
/
Copy pathSentryExecutorService.java
More file actions
188 lines (165 loc) · 5.88 KB
/
SentryExecutorService.java
File metadata and controls
188 lines (165 loc) · 5.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
package io.sentry;
import io.sentry.util.AutoClosableReentrantLock;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
@ApiStatus.Internal
public final class SentryExecutorService implements ISentryExecutorService {
/**
* ScheduledThreadPoolExecutor grows work queue by 50% each time. With the initial capacity of 16
* it will have to resize 4 times to reach 40, which is a decent middle-ground for prewarming.
* This will prevent from growing in unexpected areas of the SDK.
*/
private static final int INITIAL_QUEUE_SIZE = 40;
/**
* By default, the work queue is unbounded so it can grow as much as the memory allows. We want to
* limit it by 271 which would be x8 times growth from the default initial capacity.
*/
private static final int MAX_QUEUE_SIZE = 271;
private final @NotNull ScheduledThreadPoolExecutor executorService;
private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock();
@SuppressWarnings("UnnecessaryLambda")
private final @NotNull Runnable dummyRunnable = () -> {};
private final @Nullable SentryOptions options;
@TestOnly
SentryExecutorService(
final @NotNull ScheduledThreadPoolExecutor executorService,
final @Nullable SentryOptions options) {
this.executorService = executorService;
this.options = options;
}
public SentryExecutorService(final @Nullable SentryOptions options) {
this(new ScheduledThreadPoolExecutor(1, new SentryExecutorServiceThreadFactory()), options);
}
public SentryExecutorService() {
this(new ScheduledThreadPoolExecutor(1, new SentryExecutorServiceThreadFactory()), null);
}
@Override
public @NotNull Future<?> submit(final @NotNull Runnable runnable)
throws RejectedExecutionException {
if (executorService.getQueue().size() < MAX_QUEUE_SIZE) {
return executorService.submit(runnable);
}
if (options != null) {
options
.getLogger()
.log(SentryLevel.WARNING, "Task " + runnable + " rejected from " + executorService);
}
return new CancelledFuture<>();
}
@Override
public @NotNull <T> Future<T> submit(final @NotNull Callable<T> callable)
throws RejectedExecutionException {
if (executorService.getQueue().size() < MAX_QUEUE_SIZE) {
return executorService.submit(callable);
}
if (options != null) {
options
.getLogger()
.log(SentryLevel.WARNING, "Task " + callable + " rejected from " + executorService);
}
return new CancelledFuture<>();
}
@Override
public @NotNull Future<?> schedule(final @NotNull Runnable runnable, final long delayMillis)
throws RejectedExecutionException {
if (executorService.getQueue().size() < MAX_QUEUE_SIZE) {
return executorService.schedule(runnable, delayMillis, TimeUnit.MILLISECONDS);
}
if (options != null) {
options
.getLogger()
.log(SentryLevel.WARNING, "Task " + runnable + " rejected from " + executorService);
}
return new CancelledFuture<>();
}
@Override
public void close(final long timeoutMillis) {
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
if (!executorService.isShutdown()) {
executorService.shutdown();
try {
if (!executorService.awaitTermination(timeoutMillis, TimeUnit.MILLISECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
}
@Override
public boolean isClosed() {
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
return executorService.isShutdown();
}
}
@SuppressWarnings({"FutureReturnValueIgnored"})
@Override
public void prewarm() {
try {
executorService.submit(
() -> {
try {
// schedule a bunch of dummy runnables in the future that will never execute to
// trigger
// queue growth and then purge the queue
for (int i = 0; i < INITIAL_QUEUE_SIZE; i++) {
final Future<?> future =
executorService.schedule(dummyRunnable, 365L, TimeUnit.DAYS);
future.cancel(true);
}
executorService.purge();
} catch (RejectedExecutionException ignored) {
// ignore
}
});
} catch (RejectedExecutionException e) {
if (options != null) {
options
.getLogger()
.log(SentryLevel.WARNING, "Prewarm task rejected from " + executorService, e);
}
}
}
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++);
ret.setDaemon(true);
return ret;
}
}
private static final class CancelledFuture<T> implements Future<T> {
@Override
public boolean cancel(final boolean mayInterruptIfRunning) {
return true;
}
@Override
public boolean isCancelled() {
return true;
}
@Override
public boolean isDone() {
return true;
}
@Override
public T get() {
throw new CancellationException();
}
@Override
public T get(final long timeout, final @NotNull TimeUnit unit) {
throw new CancellationException();
}
}
}