-
Notifications
You must be signed in to change notification settings - Fork 38.9k
Description
Affects: demonstrated against 5.2.6.RELEASE
ThreadPoolTaskExecutor.setTaskDecorator() allows you to, for example, catch and log any exceptions thrown by tasks submitted to the executor. In my application this functionality is very important, because otherwise bugs that cause exceptions would be completely swallowed and lurk indefinitely, unnoticed.
The bug is that this works if you use ThreadPoolTaskExecutor.execute() to submit your task, but it doesn't work if you use ThreadPoolTaskExecutor.submit() to submit your task.
Basically, when invoking any ThreadPoolTaskExecutor method that returns a Future, there is extra wrapping involved that catches and swallows thrown exceptions before they can reach the configured TaskDecorator. This is a reversed wrapping order from that one would expect.
Here's a program that demonstrates the problem:
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
public class ThreadPoolTaskExecutorBug {
public static void main(String[] args) throws Exception {
// Setup executor
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(action -> () -> {
try {
System.out.println("invoking " + action + ".run()");
action.run();
System.out.println("successful return from " + action + ".run()");
} catch (Throwable t) {
System.out.println("caught exception from " + action + ".run(): " + t.getMessage());
}
});
executor.afterPropertiesSet();
System.out.println();
System.out.println("TEST #1");
executor.execute(new Action(1));
Thread.sleep(500);
System.out.println();
System.out.println("TEST #2");
executor.submit(new Action(2));
Thread.sleep(500);
System.out.println();
executor.shutdown();
}
public static class Action implements Runnable {
private final int id;
public Action(int id) {
this.id = id;
}
@Override
public void run() {
System.out.println(this + ": run() invoked");
System.out.println(this + ": run() throwing exception");
throw new RuntimeException("exception thrown by " + this);
}
@Override
public String toString() {
return "Action#" + this.id;
}
}
}Here's the output:
Jun 10, 2020 10:32:18 AM org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor initialize
INFO: Initializing ExecutorService
TEST #1
invoking Action#1.run()
Action#1: run() invoked
Action#1: run() throwing exception
caught exception from Action#1.run(): exception thrown by Action#1
TEST #2
invoking [email protected]()
Action#2: run() invoked
Action#2: run() throwing exception
successful return from [email protected]()
Jun 10, 2020 10:32:19 AM org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor shutdown
INFO: Shutting down ExecutorService
Note that under TEST #2 the action throws an exception, just like in TEST #1, but the configured TaskDecorator never gets a chance to catch and log the exception.