Skip to content

Commit eac37ad

Browse files
Log warnings on exceptions in Scheduler
Fixed review comments: removed todo, use FutureUtils.cancel and removed scheduler task decoration since this adds more complexity than it benefits. This is a continuation of elastic#28667, elastic#36137 and also fixes elastic#37708.
1 parent bf7259c commit eac37ad

File tree

6 files changed

+24
-84
lines changed

6 files changed

+24
-84
lines changed

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

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,11 @@
4242
import java.util.concurrent.TimeUnit;
4343
import java.util.concurrent.atomic.AtomicReference;
4444
import java.util.function.Consumer;
45-
import java.util.function.Function;
4645

4746
import static org.hamcrest.Matchers.containsString;
47+
import static org.hamcrest.Matchers.equalTo;
4848
import static org.hamcrest.Matchers.hasToString;
4949
import static org.hamcrest.Matchers.instanceOf;
50-
import static org.hamcrest.Matchers.startsWith;
5150

5251
public class EvilThreadPoolTests extends ESTestCase {
5352

@@ -166,8 +165,7 @@ protected void doRun() {
166165
assertTrue(o.isPresent());
167166
assertThat(o.get(), instanceOf(Error.class));
168167
assertThat(o.get(), hasToString(containsString("future error")));
169-
},
170-
Object::toString);
168+
});
171169
}
172170

173171
public void testExecutionExceptionOnDefaultThreadPoolTypes() throws InterruptedException {
@@ -237,13 +235,12 @@ public void testExecutionExceptionOnSinglePrioritizingThreadPoolExecutor() throw
237235
checkExecutionException(getExecuteRunner(prioritizedExecutor), true);
238236
checkExecutionException(getSubmitRunner(prioritizedExecutor), false);
239237

240-
Function<Runnable, String> logMessageFunction = r -> PrioritizedEsThreadPoolExecutor.class.getName();
241238
// bias towards timeout
242-
checkExecutionException(r -> prioritizedExecutor.execute(delayMillis(r, 10), TimeValue.ZERO, r), true, logMessageFunction);
239+
checkExecutionException(r -> prioritizedExecutor.execute(delayMillis(r, 10), TimeValue.ZERO, r), true);
243240
// race whether timeout or success (but typically biased towards success)
244-
checkExecutionException(r -> prioritizedExecutor.execute(r, TimeValue.ZERO, r), true, logMessageFunction);
241+
checkExecutionException(r -> prioritizedExecutor.execute(r, TimeValue.ZERO, r), true);
245242
// bias towards no timeout.
246-
checkExecutionException(r -> prioritizedExecutor.execute(r, TimeValue.timeValueMillis(10), r), true, logMessageFunction);
243+
checkExecutionException(r -> prioritizedExecutor.execute(r, TimeValue.timeValueMillis(10), r), true);
247244
} finally {
248245
ThreadPool.terminate(prioritizedExecutor, 10, TimeUnit.SECONDS);
249246
}
@@ -275,11 +272,6 @@ private Runnable delayMillis(Runnable r, int ms) {
275272
}
276273

277274
private void checkExecutionException(Consumer<Runnable> runner, boolean expectException) throws InterruptedException {
278-
checkExecutionException(runner, expectException, Object::toString);
279-
}
280-
private void checkExecutionException(Consumer<Runnable> runner,
281-
boolean expectException,
282-
Function<Runnable, String> logMessageFunction) throws InterruptedException {
283275
final Runnable runnable;
284276
final boolean willThrow;
285277
if (randomBoolean()) {
@@ -314,8 +306,7 @@ protected void doRun() {
314306
assertThat(o.get(), instanceOf(IllegalStateException.class));
315307
assertThat(o.get(), hasToString(containsString("future exception")));
316308
}
317-
},
318-
logMessageFunction);
309+
});
319310
}
320311

321312
Consumer<Runnable> getExecuteRunner(ExecutorService executor) {
@@ -364,8 +355,7 @@ private void runExecutionTest(
364355
final Consumer<Runnable> runner,
365356
final Runnable runnable,
366357
final boolean expectThrowable,
367-
final Consumer<Optional<Throwable>> consumer,
368-
final Function<Runnable, String> logMessageFunction) throws InterruptedException {
358+
final Consumer<Optional<Throwable>> consumer) throws InterruptedException {
369359
final AtomicReference<Throwable> throwableReference = new AtomicReference<>();
370360
final Thread.UncaughtExceptionHandler uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
371361
final CountDownLatch uncaughtExceptionHandlerLatch = new CountDownLatch(1);
@@ -398,8 +388,9 @@ public void match(LogEvent event) {
398388
if (event.getLevel() == Level.WARN) {
399389
assertThat("no other warnings than those expected",
400390
event.getMessage().getFormattedMessage(),
401-
startsWith("failed to schedule " + logMessageFunction.apply(job)));
391+
equalTo("uncaught exception in scheduled thread [" + Thread.currentThread().getName() + "]"));
402392
assertTrue(expectThrowable);
393+
assertNotNull(event.getThrown());
403394
assertTrue("only one message allowed", throwableReference.compareAndSet(null, event.getThrown()));
404395
uncaughtExceptionHandlerLatch.countDown();
405396
}

server/src/main/java/org/elasticsearch/ingest/Processor.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ class Parameters {
105105
/**
106106
* Provides scheduler support
107107
*/
108-
// todo: should we promote ScheduledCancellable to somewhere more appropriate for this external use?
109108
public final BiFunction<Long, Runnable, Scheduler.ScheduledCancellable> scheduler;
110109

111110
public Parameters(Environment env, ScriptService scriptService, AnalysisRegistry analysisRegistry, ThreadContext threadContext,

server/src/main/java/org/elasticsearch/threadpool/CancellableAdapter.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
package org.elasticsearch.threadpool;
2121

22-
import org.elasticsearch.common.SuppressForbidden;
22+
import org.elasticsearch.common.util.concurrent.FutureUtils;
2323

2424
import java.util.concurrent.Future;
2525

@@ -32,9 +32,8 @@ class CancellableAdapter implements Scheduler.Cancellable {
3232
}
3333

3434
@Override
35-
@SuppressForbidden(reason="enabler for Cancellable.cancel, never interrupts")
3635
public boolean cancel() {
37-
return future.cancel(false);
36+
return FutureUtils.cancel(future);
3837
}
3938

4039
@Override

server/src/main/java/org/elasticsearch/threadpool/Scheduler.java

Lines changed: 5 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@
3232

3333
import java.util.concurrent.Delayed;
3434
import java.util.concurrent.Future;
35-
import java.util.concurrent.FutureTask;
3635
import java.util.concurrent.RejectedExecutionHandler;
37-
import java.util.concurrent.RunnableScheduledFuture;
3836
import java.util.concurrent.ScheduledFuture;
3937
import java.util.concurrent.ScheduledThreadPoolExecutor;
4038
import java.util.concurrent.ThreadFactory;
@@ -209,7 +207,7 @@ final class ReschedulingRunnable extends AbstractRunnable implements Cancellable
209207

210208
@Override
211209
public boolean cancel() {
212-
boolean result = run;
210+
final boolean result = run;
213211
run = false;
214212
return result;
215213
}
@@ -252,7 +250,7 @@ public void onAfter() {
252250
}
253251

254252
/**
255-
* This subclass ensures to properly bubble up Throwable instances of type Error.
253+
* This subclass ensures to properly bubble up Throwable instances of type Error and logs exceptions thrown in submitted/scheduled tasks
256254
*/
257255
class SafeScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor {
258256
private static final Logger logger = LogManager.getLogger(SafeScheduledThreadPoolExecutor.class);
@@ -272,59 +270,13 @@ public SafeScheduledThreadPoolExecutor(int corePoolSize) {
272270
super(corePoolSize);
273271
}
274272

275-
/**
276-
* Decorate task with better toString.
277-
*/
278-
@Override
279-
protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) {
280-
return new ToStringRunnableScheduledFuture<>(task, runnable);
281-
}
282-
283273
@Override
284274
protected void afterExecute(Runnable r, Throwable t) {
285275
Throwable exception = EsExecutors.rethrowErrors(r);
286276
if (exception != null) {
287-
logger.warn(() -> new ParameterizedMessage("failed to schedule {}", r.toString()), exception);
288-
}
289-
}
290-
291-
private class ToStringRunnableScheduledFuture<V> extends FutureTask<V> implements RunnableScheduledFuture<V> {
292-
private final RunnableScheduledFuture<V> task;
293-
private final Runnable runnable;
294-
295-
private ToStringRunnableScheduledFuture(RunnableScheduledFuture<V> task, Runnable runnable) {
296-
super(runnable, null);
297-
this.task = task;
298-
this.runnable = runnable;
299-
}
300-
301-
@Override
302-
public boolean isPeriodic() {
303-
return task.isPeriodic();
304-
}
305-
306-
@Override
307-
public long getDelay(TimeUnit unit) {
308-
return task.getDelay(unit);
309-
}
310-
311-
@Override
312-
public int compareTo(Delayed o) {
313-
return -o.compareTo(task);
314-
}
315-
316-
@SuppressForbidden(reason = "delegation, decorating a better toString, clients should preferably use Cancellable.cancel")
317-
@Override
318-
public boolean cancel(boolean mayInterruptIfRunning) {
319-
boolean cancelled = super.cancel(mayInterruptIfRunning);
320-
if (cancelled)
321-
remove(this);
322-
return cancelled;
323-
}
324-
325-
@Override
326-
public String toString() {
327-
return runnable.toString() + ": " + task.toString();
277+
logger.warn(() ->
278+
new ParameterizedMessage("uncaught exception in scheduled thread [{}]", Thread.currentThread().getName()),
279+
exception);
328280
}
329281
}
330282
}

server/src/main/java/org/elasticsearch/transport/TransportService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1034,7 +1034,7 @@ public String toString() {
10341034
return "timeout handler for [" + requestId + "][" + action + "]";
10351035
}
10361036

1037-
public void scheduleTimeout(TimeValue timeout) {
1037+
private void scheduleTimeout(TimeValue timeout) {
10381038
this.cancellable = threadPool.schedule(this, timeout, ThreadPool.Names.GENERIC);
10391039
}
10401040
}

server/src/test/java/org/elasticsearch/threadpool/SchedulerTests.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public void testCancelOnThreadPool() {
4444
scheduleAndCancel(threadPool, executed, type));
4545
assertEquals(0, executed.get());
4646
} finally {
47-
threadPool.shutdownNow();
47+
ThreadPool.terminate(threadPool, 10, TimeUnit.SECONDS);
4848
}
4949
}
5050

@@ -54,7 +54,7 @@ private void scheduleAndCancel(ThreadPool threadPool, AtomicLong executed, Strin
5454
assertFalse(scheduled.isCancelled());
5555
assertTrue(scheduled.cancel());
5656
assertTrue(scheduled.isCancelled());
57-
assertEquals("Cancel must auto-remove",0, schedulerQueueSize(threadPool));
57+
assertEquals("Cancel must auto-remove", 0, schedulerQueueSize(threadPool));
5858
}
5959

6060
private int schedulerQueueSize(ThreadPool threadPool) {
@@ -74,10 +74,10 @@ public void testCancelOnScheduler() {
7474
assertFalse(scheduled.isCancelled());
7575
assertTrue(scheduled.cancel());
7676
assertTrue(scheduled.isCancelled());
77-
assertEquals("Cancel must auto-remove",0, executor.getQueue().size());
77+
assertEquals("Cancel must auto-remove", 0, executor.getQueue().size());
7878
assertEquals(0, executed.get());
7979
} finally {
80-
executor.shutdownNow();
80+
Scheduler.terminate(executor, 10, TimeUnit.SECONDS);
8181
}
8282
}
8383

@@ -99,9 +99,8 @@ public void testDelay() throws InterruptedException {
9999

100100
assertThat(laterDelays,
101101
Matchers.contains(initialDelays.stream().map(Matchers::lessThan).collect(Collectors.toList())));
102-
103102
} finally {
104-
threadPool.shutdownNow();
103+
ThreadPool.terminate(threadPool, 10, TimeUnit.SECONDS);
105104
}
106105
}
107106

@@ -136,7 +135,7 @@ public void testScheduledOnThreadPool() throws InterruptedException {
136135

137136
assertTrue(missingExecutions.await(30, TimeUnit.SECONDS));
138137
} finally {
139-
threadPool.shutdownNow();
138+
ThreadPool.terminate(threadPool, 10, TimeUnit.SECONDS);
140139
}
141140
}
142141

@@ -151,7 +150,7 @@ public void testScheduledOnScheduler() throws InterruptedException {
151150
scheduler.schedule(missingExecutions::countDown, TimeValue.timeValueMillis(randomInt(5)), ThreadPool.Names.SAME);
152151
assertTrue(missingExecutions.await(30, TimeUnit.SECONDS));
153152
} finally {
154-
executor.shutdownNow();
153+
Scheduler.terminate(executor, 10, TimeUnit.SECONDS);
155154
}
156155
}
157156
}

0 commit comments

Comments
 (0)