19
19
20
20
package org .elasticsearch .threadpool ;
21
21
22
+ import org .apache .logging .log4j .Level ;
23
+ import org .apache .logging .log4j .LogManager ;
24
+ import org .apache .logging .log4j .Logger ;
25
+ import org .apache .logging .log4j .core .LogEvent ;
26
+ import org .elasticsearch .common .logging .Loggers ;
22
27
import org .elasticsearch .common .settings .Settings ;
23
28
import org .elasticsearch .common .unit .TimeValue ;
24
29
import org .elasticsearch .common .util .concurrent .AbstractRunnable ;
25
30
import org .elasticsearch .common .util .concurrent .EsExecutors ;
26
31
import org .elasticsearch .common .util .concurrent .EsThreadPoolExecutor ;
27
32
import org .elasticsearch .common .util .concurrent .PrioritizedEsThreadPoolExecutor ;
28
33
import org .elasticsearch .test .ESTestCase ;
34
+ import org .elasticsearch .test .MockLogAppender ;
29
35
import org .junit .After ;
30
36
import org .junit .Before ;
31
37
38
44
import java .util .function .Consumer ;
39
45
40
46
import static org .hamcrest .Matchers .containsString ;
47
+ import static org .hamcrest .Matchers .equalTo ;
41
48
import static org .hamcrest .Matchers .hasToString ;
42
49
import static org .hamcrest .Matchers .instanceOf ;
43
50
@@ -108,7 +115,12 @@ public void testExecutionErrorOnSinglePrioritizingThreadPoolExecutor() throws In
108
115
try {
109
116
checkExecutionError (getExecuteRunner (prioritizedExecutor ));
110
117
checkExecutionError (getSubmitRunner (prioritizedExecutor ));
118
+ // bias towards timeout
119
+ checkExecutionError (r -> prioritizedExecutor .execute (delayMillis (r , 10 ), TimeValue .ZERO , r ));
120
+ // race whether timeout or success (but typically biased towards success)
111
121
checkExecutionError (r -> prioritizedExecutor .execute (r , TimeValue .ZERO , r ));
122
+ // bias towards no timeout.
123
+ checkExecutionError (r -> prioritizedExecutor .execute (r , TimeValue .timeValueMillis (10 ), r ));
112
124
} finally {
113
125
ThreadPool .terminate (prioritizedExecutor , 10 , TimeUnit .SECONDS );
114
126
}
@@ -170,10 +182,7 @@ public void testExecutionExceptionOnDefaultThreadPoolTypes() throws InterruptedE
170
182
final boolean expectExceptionOnSchedule =
171
183
// fixed_auto_queue_size wraps stuff into TimedRunnable, which is an AbstractRunnable
172
184
// TODO: this is dangerous as it will silently swallow exceptions, and possibly miss calling a response listener
173
- ThreadPool .THREAD_POOL_TYPES .get (executor ) != ThreadPool .ThreadPoolType .FIXED_AUTO_QUEUE_SIZE
174
- // scheduler just swallows the exception here
175
- // TODO: bubble these exceptions up
176
- && ThreadPool .THREAD_POOL_TYPES .get (executor ) != ThreadPool .ThreadPoolType .DIRECT ;
185
+ ThreadPool .THREAD_POOL_TYPES .get (executor ) != ThreadPool .ThreadPoolType .FIXED_AUTO_QUEUE_SIZE ;
177
186
checkExecutionException (getScheduleRunner (executor ), expectExceptionOnSchedule );
178
187
}
179
188
}
@@ -219,14 +228,19 @@ public void testExecutionExceptionOnAutoQueueFixedESThreadPoolExecutor() throws
219
228
}
220
229
}
221
230
222
- @ AwaitsFix (bugUrl = "https://github.com/elastic/elasticsearch/issues/37708" )
223
231
public void testExecutionExceptionOnSinglePrioritizingThreadPoolExecutor () throws InterruptedException {
224
232
final PrioritizedEsThreadPoolExecutor prioritizedExecutor = EsExecutors .newSinglePrioritizing ("test" ,
225
233
EsExecutors .daemonThreadFactory ("test" ), threadPool .getThreadContext (), threadPool .scheduler ());
226
234
try {
227
235
checkExecutionException (getExecuteRunner (prioritizedExecutor ), true );
228
236
checkExecutionException (getSubmitRunner (prioritizedExecutor ), false );
237
+
238
+ // bias towards timeout
239
+ checkExecutionException (r -> prioritizedExecutor .execute (delayMillis (r , 10 ), TimeValue .ZERO , r ), true );
240
+ // race whether timeout or success (but typically biased towards success)
229
241
checkExecutionException (r -> prioritizedExecutor .execute (r , TimeValue .ZERO , r ), true );
242
+ // bias towards no timeout.
243
+ checkExecutionException (r -> prioritizedExecutor .execute (r , TimeValue .timeValueMillis (10 ), r ), true );
230
244
} finally {
231
245
ThreadPool .terminate (prioritizedExecutor , 10 , TimeUnit .SECONDS );
232
246
}
@@ -235,26 +249,39 @@ public void testExecutionExceptionOnSinglePrioritizingThreadPoolExecutor() throw
235
249
public void testExecutionExceptionOnScheduler () throws InterruptedException {
236
250
final ScheduledThreadPoolExecutor scheduler = Scheduler .initScheduler (Settings .EMPTY );
237
251
try {
238
- // scheduler just swallows the exceptions
239
- // TODO: bubble these exceptions up
240
- checkExecutionException (getExecuteRunner (scheduler ), false );
241
- checkExecutionException (getSubmitRunner (scheduler ), false );
242
- checkExecutionException (r -> scheduler .schedule (r , randomFrom (0 , 1 ), TimeUnit .MILLISECONDS ), false );
252
+ checkExecutionException (getExecuteRunner (scheduler ), true );
253
+ // while submit does return a Future, we choose to log exceptions anyway,
254
+ // since this is the semi-internal SafeScheduledThreadPoolExecutor that is being used,
255
+ // which also logs exceptions for schedule calls.
256
+ checkExecutionException (getSubmitRunner (scheduler ), true );
257
+ checkExecutionException (r -> scheduler .schedule (r , randomFrom (0 , 1 ), TimeUnit .MILLISECONDS ), true );
243
258
} finally {
244
259
Scheduler .terminate (scheduler , 10 , TimeUnit .SECONDS );
245
260
}
246
261
}
247
262
263
+ private Runnable delayMillis (Runnable r , int ms ) {
264
+ return () -> {
265
+ try {
266
+ Thread .sleep (ms );
267
+ } catch (InterruptedException e ) {
268
+ Thread .currentThread ().interrupt ();
269
+ }
270
+ r .run ();
271
+ };
272
+ }
273
+
248
274
private void checkExecutionException (Consumer <Runnable > runner , boolean expectException ) throws InterruptedException {
249
- logger .info ("checking exception for {}" , runner );
250
275
final Runnable runnable ;
251
276
final boolean willThrow ;
252
277
if (randomBoolean ()) {
278
+ logger .info ("checking direct exception for {}" , runner );
253
279
runnable = () -> {
254
280
throw new IllegalStateException ("future exception" );
255
281
};
256
282
willThrow = expectException ;
257
283
} else {
284
+ logger .info ("checking abstract runnable exception for {}" , runner );
258
285
runnable = new AbstractRunnable () {
259
286
@ Override
260
287
public void onFailure (Exception e ) {
@@ -275,6 +302,7 @@ protected void doRun() {
275
302
o -> {
276
303
assertEquals (willThrow , o .isPresent ());
277
304
if (willThrow ) {
305
+ if (o .get () instanceof Error ) throw (Error ) o .get ();
278
306
assertThat (o .get (), instanceOf (IllegalStateException .class ));
279
307
assertThat (o .get (), hasToString (containsString ("future exception" )));
280
308
}
@@ -313,7 +341,7 @@ Consumer<Runnable> getScheduleRunner(String executor) {
313
341
return new Consumer <Runnable >() {
314
342
@ Override
315
343
public void accept (Runnable runnable ) {
316
- threadPool .schedule (randomFrom (TimeValue .ZERO , TimeValue .timeValueMillis (1 )), executor , runnable );
344
+ threadPool .schedule (runnable , randomFrom (TimeValue .ZERO , TimeValue .timeValueMillis (1 )), executor );
317
345
}
318
346
319
347
@ Override
@@ -324,42 +352,77 @@ public String toString() {
324
352
}
325
353
326
354
private void runExecutionTest (
327
- final Consumer <Runnable > runner ,
328
- final Runnable runnable ,
329
- final boolean expectThrowable ,
330
- final Consumer <Optional <Throwable >> consumer ) throws InterruptedException {
355
+ final Consumer <Runnable > runner ,
356
+ final Runnable runnable ,
357
+ final boolean expectThrowable ,
358
+ final Consumer <Optional <Throwable >> consumer ) throws InterruptedException {
331
359
final AtomicReference <Throwable > throwableReference = new AtomicReference <>();
332
360
final Thread .UncaughtExceptionHandler uncaughtExceptionHandler = Thread .getDefaultUncaughtExceptionHandler ();
333
361
final CountDownLatch uncaughtExceptionHandlerLatch = new CountDownLatch (1 );
334
362
335
363
try {
336
364
Thread .setDefaultUncaughtExceptionHandler ((t , e ) -> {
337
365
assertTrue (expectThrowable );
338
- throwableReference .set ( e );
366
+ assertTrue ( "Only one message allowed" , throwableReference .compareAndSet ( null , e ) );
339
367
uncaughtExceptionHandlerLatch .countDown ();
340
368
});
341
369
342
370
final CountDownLatch supplierLatch = new CountDownLatch (1 );
343
371
344
- try {
345
- runner .accept (() -> {
346
- try {
347
- runnable .run ();
348
- } finally {
349
- supplierLatch .countDown ();
372
+ Runnable job = () -> {
373
+ try {
374
+ runnable .run ();
375
+ } finally {
376
+ supplierLatch .countDown ();
377
+ }
378
+ };
379
+
380
+ // snoop on logging to also handle the cases where exceptions are simply logged in Scheduler.
381
+ final Logger schedulerLogger = LogManager .getLogger (Scheduler .SafeScheduledThreadPoolExecutor .class );
382
+ final MockLogAppender appender = new MockLogAppender ();
383
+ appender .addExpectation (
384
+ new MockLogAppender .LoggingExpectation () {
385
+ @ Override
386
+ public void match (LogEvent event ) {
387
+ if (event .getLevel () == Level .WARN ) {
388
+ assertThat ("no other warnings than those expected" ,
389
+ event .getMessage ().getFormattedMessage (),
390
+ equalTo ("uncaught exception in scheduled thread [" + Thread .currentThread ().getName () + "]" ));
391
+ assertTrue (expectThrowable );
392
+ assertNotNull (event .getThrown ());
393
+ assertTrue ("only one message allowed" , throwableReference .compareAndSet (null , event .getThrown ()));
394
+ uncaughtExceptionHandlerLatch .countDown ();
395
+ }
396
+ }
397
+
398
+ @ Override
399
+ public void assertMatched () {
350
400
}
351
401
});
352
- } catch (Throwable t ) {
353
- consumer .accept (Optional .of (t ));
354
- return ;
355
- }
356
402
357
- supplierLatch .await ();
403
+ appender .start ();
404
+ Loggers .addAppender (schedulerLogger , appender );
405
+ try {
406
+ try {
407
+ runner .accept (job );
408
+ } catch (Throwable t ) {
409
+ consumer .accept (Optional .of (t ));
410
+ return ;
411
+ }
412
+
413
+ supplierLatch .await ();
358
414
359
- if (expectThrowable ) {
360
- uncaughtExceptionHandlerLatch .await ();
415
+ if (expectThrowable ) {
416
+ uncaughtExceptionHandlerLatch .await ();
417
+ }
418
+ } finally {
419
+ Loggers .removeAppender (schedulerLogger , appender );
420
+ appender .stop ();
361
421
}
422
+
362
423
consumer .accept (Optional .ofNullable (throwableReference .get ()));
424
+ } catch (IllegalAccessException e ) {
425
+ throw new RuntimeException (e );
363
426
} finally {
364
427
Thread .setDefaultUncaughtExceptionHandler (uncaughtExceptionHandler );
365
428
}
0 commit comments