The patch intends to mitigate the unexpected death of threads, and mitigate the impact
that they have on your application.
To help illustrate illustrate this, here is an example project with a very
nasty thread eating up all memory:
Compile and run this application with -Xmx16m.
You should see something like the following:
The application is stuck, we are no longer seeing any main: OK messages.
No stack traces, nothing.
The reason is that out coordinator thread allocates memory for its message,
this means that it could be the target of an OutOfMemoryError when the
allocation fails because BadThread has locked up all available memory and is
refusing to die.
This state is when it gets interesting. ThreadPoolExecutor will, as per documentation, happily catch and
swallow any exception being thrown in one of its tasks.
It is explicitly left to the developer to handle this.
This leaves us with a dead coordinator thread at the other end of the Queue, and main
is left to its own devices forever. :(.
This patch overrides the afterExecute
method. A hook designed to allow for custom behavior after the completion of
tasks.
Run the project again, and you should see the following:
Errors
I want to emphasise that OutOfMemoryError is generally not an error that you
can safely recover from. There are no guarantees that the thread responsible
for eating up your memory is the target for this error. Even if that is the
case, this thread might become important at a later stage in its life.
In my opinion, the most reasonable thing to do is to give up.
An Error is a subclass of Throwable that indicates serious problems that a
reasonable application should not try to catch. Most such errors are abnormal
conditions.
At this stage you might be tempted to attempt a clean shutdown of your
application on errors.
This might work. But we might also be in a state where a thread critical
towards the clean shutdown of your application is no longer alive.
There might not be any memory left to support a complex shutdown. Attempting it
could lead to your cleanup attempt crashing leading us back to where we
started.
If you want to cover manually created threads, you can make use of
Thread#setDefaultUncaughtExceptionHandler.
Just remember, this still does not cover thread pools.
On a final note, if you are a library developer: Please don’t hide your thread
pools from us.