CyclicBarrier example: error handling
In our introduction to CyclicBarrier, we mentioned that
one potentially useful feature is that if, while waiting at the barrier, one of the
waiting threads is interrupted or times out, then all of the other waiting threads wake
up with a BrokenBarrierException.
But what happens if an exception occurs during a thread's execution of the actual
task code— in other words, while it isn't in the await()
call? Well in this case, one solution is to deliberately interrupt the thread
and then call await()! Our thread will immediately return from the await()
call with an InterruptedException (which we obviously don't care about),
and all other threads will wake up
from await() with a BrokenBarrierException.
The pattern looks as follows:
private volatile Throwable error;
...
public void run() {
try {
... perform task
barrier.await();
... perform task
barrier.await();
} catch (InterruptedException e) {
// don't care-- controller thread will get
// BrokenBarrierException
} catch (BrokenBarrierException e) {
// ditto
} catch (Throwable t) {
// some other exception occurred during our task
error = t;
Thread.currentThread().interrupt();
try {
barrier.await();
} catch (Exception e) {}
}
}
This is one of the few cases where we probably just want to "swallow" various
exceptions, since the BrokenBarrierException will be thrown in all
the other threads, including our "controller" thread. In the case of catching some
other exception because it occurred during our task code, we may want
to save it to a variable (as the error variable here). Then the controller
thread, on being awoken with a BrokenBarrierException, can find the thread
with the error variable set and throw the corresponding exception to the
caller.
Error in the result amalgamation Runnable
There's actually a race condition that we haven't dealt with in the above code.
When the last thread to get to the barrier calls the await() method,
the barrier's Runnable (if any) is run before the await()
method returns. If an unchecked exception is thrown by that Runnable,
then the barrier is broken— in other words, other waiting threads potentially
signalled— before that exception is thrown up to the
caller of await(). In other words, the controller thread could be awoken
before the error variable has been set.
To get round this problem, albeit in a slightly inelegant way, we can
set another variable specially for errors that occur inside the Runnable.
So that method looks as follows:
private volatile RuntimeException errorInAmalgRunnable;
CyclicBarrier barrier = new CyclicBarrier(noThreads, new Runnable() {
public void run() {
try {
... amalgamate results ...
} catch (RuntimeException e) {
errorInAmalgRunnable = e;
throw e;
}
}
});
You'll see elements of this error handling technique in our
parallel sort implementation.
If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.
Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.