In this blogpost I try to explain how the Spring TaskExecutor should be improved.
I’m interested in concurrency control and also in the Spring Framework. I have used the new concurrency library of Java 5 in quite a few Spring based projects and it works like a dream. One of the interfaces I use quite often is the Executor from Java 5. It is great for separating the submission of tasks from the execution mechanism for running the task. The consequence is that you don’t have to fiddle around with creating threads yourself and this makes your components a lot cleaner, more customizable and better testable.
It is interesting to see that Spring 2 also has support for this concept, in the form of the TaskExecutor. The advantage of using the TaskExecutor instead of the Java 5 specific Executor is that it doesn’t make code dependant of a specific implementation, eg:
- JSR 166: part of Java 5.
- backport of JSR 166: great if you still need to work with Java 1.4.
- CommonJ WorkManager: if you work in an environment where it is not allowed or acceptable to create unmanaged threads. There are plans to make it possible to use JSR-166 just in just these kind of environments.
- one of the Spring 2 implementations.
In most cases I prefer using the Executor (or one of the subinterfaces like the ExecutorService) because I often need more control, like waiting for the completion of a task. Often I have the freedom to use the concurrency library that suits my needs, so why not make use of the library directly. But if you don’t have this freedom, the TaskExecutor is a good solution.
What is the problem?
The problem is that there is no clearly defined way to detect if a task is not accepted by a TaskExecutor implementation. There are various reasons why a task can’t be accepted:
- if a spring executor implementation uses a workqueue, for example the ThreadPoolTaskExecutor, it could decide to rejects a task when the workqueue is full. If there is no limit on the size of the workqueue (SimpleThreadPoolTaskExecutor or a ThreadPoolExecutor with an unbound workqueue), it could lead to resource problems like running out of memory. This means that the system doesn’t degrade gracefully under heavy load.
- if a spring executor is shutting down, is not started or even when it is momentarily pausing.
That is why the execute method of the Executor throws a java.util.concurrent.RejectedExecutionException to signal when a task is rejected. However the execute method of the TaskExecutor, doesn’t define an exception that is thrown when a task is rejected.
You might wonder if is a good thing to deal with this exception, instead of relying on a some kind of general exception handler. I think it is good to create the possibility to deal with the exception immediately, because it is not a situation where all bets are off, like a programming or database exception. When a task can’t be executed, you give the client (another java object) the option to catch it and deal with it. And in most cases the client is aware of threading because it is dealing with an asynchronous call.
Try to make the number of components that are aware of threading, as small as possible. This makes the system a lot easier to deal with because you don’t have to worry about it all the time. In most cases, isolation (confinement) and immutability are your biggest friends to reduce concurrency control related complexity.
The solution is letting the execute method of the TaskExecutor throw a Spring specific unchecked exception, eg the : RejectedTaskExecutionException. This solution is quite simple and they already do it in other parts of the system like:
Unification of different exception hierarchies into a single one, makes it more reliable to switch between implementations because code does not depend on implementation specific exceptions. And because the exception is not checked:
- clients are not forced to deal with it if they don’t need to
- it doesn’t break any existing implementations of the TaskExecutor interface
So I wonder why they have not added it. I placed a JIRA issue for it some time ago, but it hasn’t been fixed although Spring 2 has been released.