Skip to content

Commit 4f941c6

Browse files
committed
Do not swallow exceptions in TimedRunnable (#39856)
Executors of type fixed_auto_queue_size (i.e. search / search_throttled) wrap runnables into TimedRunnable, which is an AbstractRunnable. This is dangerous as it might silently swallow exceptions, and possibly miss calling a response listener. While this has not triggered any failures in the tests I have run so far, it might help uncover future problems. Follow-up to #36137
1 parent 292eb8b commit 4f941c6

File tree

3 files changed

+39
-12
lines changed

3 files changed

+39
-12
lines changed

qa/evil-tests/src/test/java/org/elasticsearch/threadpool/EvilThreadPoolTests.java

+3-12
Original file line numberDiff line numberDiff line change
@@ -163,20 +163,12 @@ protected void doRun() {
163163

164164
public void testExecutionExceptionOnDefaultThreadPoolTypes() throws InterruptedException {
165165
for (String executor : ThreadPool.THREAD_POOL_TYPES.keySet()) {
166-
final boolean expectExceptionOnExecute =
167-
// fixed_auto_queue_size wraps stuff into TimedRunnable, which is an AbstractRunnable
168-
// TODO: this is dangerous as it will silently swallow exceptions, and possibly miss calling a response listener
169-
ThreadPool.THREAD_POOL_TYPES.get(executor) != ThreadPool.ThreadPoolType.FIXED_AUTO_QUEUE_SIZE;
170-
checkExecutionException(getExecuteRunner(threadPool.executor(executor)), expectExceptionOnExecute);
166+
checkExecutionException(getExecuteRunner(threadPool.executor(executor)), true);
171167

172168
// here, it's ok for the exception not to bubble up. Accessing the future will yield the exception
173169
checkExecutionException(getSubmitRunner(threadPool.executor(executor)), false);
174170

175-
final boolean expectExceptionOnSchedule =
176-
// fixed_auto_queue_size wraps stuff into TimedRunnable, which is an AbstractRunnable
177-
// TODO: this is dangerous as it will silently swallow exceptions, and possibly miss calling a response listener
178-
ThreadPool.THREAD_POOL_TYPES.get(executor) != ThreadPool.ThreadPoolType.FIXED_AUTO_QUEUE_SIZE;
179-
checkExecutionException(getScheduleRunner(executor), expectExceptionOnSchedule);
171+
checkExecutionException(getScheduleRunner(executor), true);
180172
}
181173
}
182174

@@ -213,8 +205,7 @@ public void testExecutionExceptionOnAutoQueueFixedESThreadPoolExecutor() throws
213205
1, 1, 1, TimeValue.timeValueSeconds(10), EsExecutors.daemonThreadFactory("test"), threadPool.getThreadContext());
214206
try {
215207
// fixed_auto_queue_size wraps stuff into TimedRunnable, which is an AbstractRunnable
216-
// TODO: this is dangerous as it will silently swallow exceptions, and possibly miss calling a response listener
217-
checkExecutionException(getExecuteRunner(autoQueueFixedExecutor), false);
208+
checkExecutionException(getExecuteRunner(autoQueueFixedExecutor), true);
218209
checkExecutionException(getSubmitRunner(autoQueueFixedExecutor), false);
219210
} finally {
220211
ThreadPool.terminate(autoQueueFixedExecutor, 10, TimeUnit.SECONDS);

server/src/main/java/org/elasticsearch/common/util/concurrent/TimedRunnable.java

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
package org.elasticsearch.common.util.concurrent;
2121

22+
import org.elasticsearch.ExceptionsHelper;
23+
2224
/**
2325
* A class used to wrap a {@code Runnable} that allows capturing the time of the task since creation
2426
* through execution as well as only execution time.
@@ -48,6 +50,8 @@ public void doRun() {
4850
public void onRejection(final Exception e) {
4951
if (original instanceof AbstractRunnable) {
5052
((AbstractRunnable) original).onRejection(e);
53+
} else {
54+
ExceptionsHelper.reThrowIfNotNull(e);
5155
}
5256
}
5357

@@ -62,6 +66,8 @@ public void onAfter() {
6266
public void onFailure(final Exception e) {
6367
if (original instanceof AbstractRunnable) {
6468
((AbstractRunnable) original).onFailure(e);
69+
} else {
70+
ExceptionsHelper.reThrowIfNotNull(e);
6571
}
6672
}
6773

server/src/test/java/org/elasticsearch/common/util/concurrent/TimedRunnableTests.java

+30
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,34 @@ protected void doRun() throws Exception {
114114
assertTrue(onAfter.get());
115115
}
116116

117+
public void testTimedRunnableRethrowsExceptionWhenNotAbstractRunnable() {
118+
final AtomicBoolean hasRun = new AtomicBoolean();
119+
final RuntimeException exception = new RuntimeException();
120+
121+
final Runnable runnable = () -> {
122+
hasRun.set(true);
123+
throw exception;
124+
};
125+
126+
final TimedRunnable timedRunnable = new TimedRunnable(runnable);
127+
final RuntimeException thrown = expectThrows(RuntimeException.class, () -> timedRunnable.run());
128+
assertTrue(hasRun.get());
129+
assertSame(exception, thrown);
130+
}
131+
132+
public void testTimedRunnableRethrowsRejectionWhenNotAbstractRunnable() {
133+
final AtomicBoolean hasRun = new AtomicBoolean();
134+
final RuntimeException exception = new RuntimeException();
135+
136+
final Runnable runnable = () -> {
137+
hasRun.set(true);
138+
throw new AssertionError("should not run");
139+
};
140+
141+
final TimedRunnable timedRunnable = new TimedRunnable(runnable);
142+
final RuntimeException thrown = expectThrows(RuntimeException.class, () -> timedRunnable.onRejection(exception));
143+
assertFalse(hasRun.get());
144+
assertSame(exception, thrown);
145+
}
146+
117147
}

0 commit comments

Comments
 (0)