views:

101

answers:

3

After finding that FutureTask running in a Executors.newCachedThreadPool() on Java 1.6 (and from Eclipse) swallows exceptions in the Runnable.run() method, I've tried to come up with a way to catch these without adding throw/catch to all my Runnable implementations.

The API suggests that overriding FutureTask.setException() should help in this:

Causes this future to report an ExecutionException with the given throwable as its cause, unless this Future has already been set or has been cancelled. This method is invoked internally by the run method upon failure of the computation.

However this method doesn't seem to be called (running with the debugger shows the exception is caught by FutureTask, but setException isn't called). I've written the following program to reproduce my problem:

public class RunTest {
public static void main(String[] args)
{
    MyFutureTask t = new MyFutureTask(new Runnable() {

        @Override
        public void run() {
            throw new RuntimeException("Unchecked exception");

        }
    });

    ExecutorService service = Executors.newCachedThreadPool();
    service.submit(t);
}
}



public class MyFutureTask extends FutureTask<Object> {

public MyFutureTask(Runnable r)
{
    super(r, null);
}

@Override
protected void setException(Throwable t)
{
    super.setException(t);
    System.out.println("Exception: " + t);
}
}

My main question is: How can I catch Exceptions thrown in a FutureTask? Why doesn't setException get called?

Also I would like to know why the Thread.UncaughtExceptionHandler mechanism isn't used by FutureTask, is there any reason for this?

+1  A: 

I have looked at the source code of FutureTask and could not find where setException is being called.
There is an innerSetException method from FutureTask.Sync (inner class of FutureTask) that is being called in case of an Throwable being thrown by the run method. This method is also being called in setException.
So it seams like the javadoc is not correct (or very hard to understand...).

Carlos Heuberger
After looking some more at the terribly slow sun bugs database: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6464365Apparantly it is known since 2006 and still not fixed (eventhough it is stated as fixed)
Thirler
@Thirler - the bug report clearly says that it is fixed **in Java 7**.
Stephen C
+2  A: 

setException probably isn't made for overriding, but is provided to let you set the result to an exception, should the need arise. What you want to do is override the done() method and try to get the result:

public class MyFutureTask extends FutureTask<Object> {

    public MyFutureTask(Runnable r) {
        super(r, null);
    }

    @Override
    protected void done() {
        try {
            if (!isCancelled()) get();
        } catch (ExecutionException e) {
            // Exception occurred, deal with it
            System.out.println("Exception: " + e.getCause());
        } catch (InterruptedException e) {
            // Shouldn't happen, we're invoked when computation is finished
            throw new AssertionError(e);
        }
    }
}
gustafc
This worked, thanks.
Thirler
+1  A: 

Have you tried using an UncaughtExceptionHandler?

  • You need to implement the UncaughtExceptionHandler interface.
  • To set an UncaughtExceptionHandler for pool threads, provide a ThreadFactory in the Executor.newCachedThreadPool(ThreadFactory) call.
  • You can set the UncaughtExceptionHandler for the created thread via setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)

Submit the tasks with ExecutorService.execute, because only exceptions thrown from tasks submitted with execute make it to the uncaught exception handler. For Tasks submitted with ExecutorService.submit any thrown exception is considered to be part of the task's return value. If a task submitted with submit terminates with an exception, it is rethrown when calling Future.get, wrapped in an ExecutionException

Soundlink