[concurrency-interest] Unreported RuntimeException if Future.get() is never invoked

Kevin Condon conivek at gmail.com
Thu Dec 21 09:11:00 EST 2006


If you use ExecutorService.submit() to start a task, but you never
invoke the returned Future's get(), then you'll never see any evidence
of RuntimeExceptions thrown during task execution.  Undetected
exceptions are bad and it was pretty difficult to figure out what was
happening in my own code.  I'm currently correcting several places in
our codebase where this vulnerability exists and thought it might be a
public service to point out to others the pit I fell into.  :)

There are a variety of reasons that you want a Future but may never
invoke get().  One situation where this could happen is a concurrent
task with a cancel option, so you only need the Future for the cancel.
 Here is a skeleton example:

public class CancellableTask implements Runnable {
  private final ExecutorService exec;
  private volatile Future<?> future = null;

  public CancellableTask(ExecutorService exec) {
    this.exec = exec;
  }

  public void execute() {
    // example ignores race on future for concurrent task executions
    future = exec.submit(this);
  }

  public void cancel() {
    if (future != null) {
      future.cancel(true);
    }
  }

  public void run() {
    boolean error = false;
    // work to be done ...
    if (error) {
      // this will not be logged!!!!
      throw IllegalStateException("Secret exception");
    }
  }
}

A co-worker and I, JCiP in hand, discovered that the solution to this
is to extend FutureTask overriding the done() hook to check/log the
exception and then use exec.execute(futureTask) instead of
submit(this).  To do this, change the execute() method:

  public void execute() {
    FutureTask<?> futureTask = new FutureTask<?>(this, null) {
      protected void done() {
        try {
          get();
        } catch (ExecutionException ex) {
          ex.getCause().printStackTrace();  // logging or handling of
your choice
        } catch (CancellationException ex) {
          // ignore unless you want to log task cancellation here
        } catch (InterruptedException ex) {
          // ignore; we're done, so get() won't block or be interrupted
        }
      }
    };
    future = futureTask;
    exec.execute(futureTask);
  }

Hope this helps someone to avoid stumbling into this, because without
knowing this can happen the problem can go entirely unnoticed.

Regards,
Kevin


More information about the Concurrency-interest mailing list