[concurrency-interest] Future.get exception mapping

Joe Bowbeer joe.bowbeer at gmail.com
Thu Jul 14 23:04:43 EDT 2011


I think we both agree that there is no perfect way to compress remote and
local exceptions.

I think the programmer-friendly answer depends on the problem domain.  I
assume your problem domain is in-lining of RPCs?

> I'm wondering what you do with InterruptedException when calling
Future.get in MyService:

I would declare that it throws InterruptedException and let it propagate to
the (higher) level in my code where interrupts are dealt with.

A very common case however is that the caller will never be interrupted, and
I think it would be convenient to have a form that operated under this
assumption -- in which case I would throw an AssertionError if an
InterruptedException were thrown.  If the assumption really is that the code
will not be interrupted, why *not* hide the exception and enforce the
assumption with an assertion?

Here are more comments on your revised policy:

* Any InterruptedException is wrapped in an X (after restoring the
interrupt).

Why not rethrow as UncheckedInterruptedException?  If you want the
programmers to deal with interrupt, then leave the exception alone...  But
if you don't want them to deal with it (or interrupts will never happen),
then convert to an unchecked exception or an error.  Either of these seems
preferable than combining the interrupt with an execution-specific
exception.

* Any CancellationException or other raw RuntimeException is propagated
untouched.

OK - they're all local exceptions.

* Any ExecutionException has its cause unwrapped and rewrapped, with the new
type depending on the type of the cause:
** If the cause was a checked exception, it is wrapped in an X.
** If the cause was an unchecked exception, it is wrapped in
an UncheckedExecutionException.
** If the cause was an error, it is wrapped in an ExecutionError.

I would re-wrap everything in X.  Let the user decide if they want to make X
checked or unchecked.

I can see some utility to the UncheckedExceptionException and
ExecutionError, but I'm also concerned about confusion between remote and
local instances of these.  Consider chained RPCs and what happens to their
exceptions...

Joe

On Thu, Jul 14, 2011 at 6:51 PM, Chris Povirk wrote:

> > Re-wrapping un/checked exceptions doesn't concern me as much as I am
> > concerned by mixing remote "execution" exceptions with local
> (non-execution)
> > exceptions, where execution exceptions were thrown by the future's
> callable
> > executing on the executor thread, and local exceptions were thrown by the
> > calling thread.
> > I like the idea of allowing the caller to provide their own custom
> > ExecutionException, but I would prefer if it were used only for execution
> > exceptions...
> >> Any InterruptedException is wrapped in an X (after restoring the
> >> interrupt).
> > I don't like this because it mixes remote and local.
>
> This is simultaneously the heart of the method and the part I least
> want to talk about :)  By that, I basically mean that I have my heart
> set on adding a method that mixes remote and local exceptions.  (So
> really I'm happy to talk about, but I may be a poor listener :))
> That's because my primary goal is to collapse all the Future.get
> exceptions into one exception type.  Many users of Future won't want
> this, of course, but writing 2-3 catch blocks when all I care about it
> success vs. failure drives me crazy, so I just want to solve that
> case.  One way to do this is to change getUninterruptibly to also
> accept an exception mapper for ExecutionException only.  This solves
> the problem at the cost of messing with the interrupt system (see
> below) -- at least if you don't use the timed get() and introduce
> TimeoutException.
>
> > And isn't the
> > InterruptedException already handled by your Uninterruptible form of
> Future?
> > (I don't want all Future.gets to be uninterruptible -- what's the point
> of
> > interrupting if nothing actually responds to it?)
>
> The new method would be a competitor to getUninterruptibly, and this
> is by design: I'm hoping that it will eat into getUninterruptibly's
> market share so that more Future uses remain interruptible.
>
> Setting aside the question of the new Guava method, I'm wondering what
> you do with InterruptedException when calling Future.get in MyService:
>
> 1. use getUninterruptibly
> 2. catch it, restore interrupt, and throw MyServiceException
> 3. declare each MyService method to throw both MyServiceException and
> InterruptedException[*]
> 4. something else?
>
> If there's another option out there, maybe the new Guava method should
> do it instead.  Ideas, anyone?
>
> [*] Of course, (3) is an option, but it feels like an abstraction
> violation in some cases (in the same way that throwing IOException
> from MyDatabaseService would be: Why should my callers care if I use
> threads internally and in such a way that allows for interruption?).
> Further, because interruption means cancellation for all practical
> purposes, it _kind of_ doesn't matter what we do when we're
> interrupted, as long as we stop doing it quickly.  Sure, if I'm
> writing low-level concurrency code, I might need to catch
> InterruptedException specifically so that I can restore some
> invariants before blowing up, but if I'm writing MyDatabaseService, my
> users already expect for me to throw exceptions, so they either won't
> have these invariants or will already have a try-finally to deal with
> them.  The Guava method exists for the MyDatabaseService guy.  (And
> here comes my secret motivation: A lot of MyDatabaseService guys are
> going to write "catch (InterruptedException e) {}" and thus neither
> abort quickly nor finish the job.  Interruption is complicated :( ...
> )
>
> > Thoughts on revised policy:
> > Your revised policy only allows the programmer to provide a custom class
> for
> > an ExecutionException whose cause was checked.  But it's not clear to me
> > that a checked/unchecked exception in the context of the executor is also
> a
> > checked/unchecked exception in the context of the caller.  In some ways,
> the
> > revised policy is making it more difficult for the programmer who cares
> > about such things.
>
> True, the caller might well want to convert unchecked exceptions to
> checked.  I've often wanted to convert a NumberFormatException into
> some kind of checked exception, for instance, and it might be better
> overall if MyDataService threw only MyDataServiceException (instead of
> NPE, etc.) so that, when it failed, I could treat a NPE like any other
> "I couldn't get data" failure, rather than as a failure of unknown
> source that causes us to abort rendering the web app entirely.  Then
> again, I probably track statistics for MyDataServiceExceptions
> separately from RuntimeExceptions, since the former may indicate
> transient timeouts but the latter bugs, so I don't necessarily want to
> convert the latter to the former.
>
> Overall, I guess I view "exception came from another thread" as an
> implementation detail that shouldn't affect whether it's checked or
> unchecked from the perspective of the caller.  I would find it equally
> useful (and equally dangerous) to be able to automatically convert
> from unchecked to checked exceptions for in-thread operations.
> Ultimately, such conversions feel more dangerous than useful to me,
> but I'm far from certain.
>
> > How about providing a form of Future.get where the caller supplies an
> > exception factory (or listener)?  Then they can decide on the execution
> > class at the point of failure.
>
> If you mean what I think you mean, the answer is that I haven't found
> a library to save a lot of code over the manual approach.  Compare:
>
>  public static <V> V getWithMyException(Future<V> future) throws
> MyException {
>   checkNotNull(future);
>   try {
>     return future.get();
>   } catch (InterruptedException e) {
>     currentThread().interrupt();
>     throw new MyException(e);
>   } catch (CancellationException e) {
>     throw e;
>   } catch (ExecutionException e) {
>     throw new MyException(e.getCause());
>   }
>  }
>
>  public static <V> V getWithMyException(Future<V> future) throws
> MyException {
>   return Futures.get(future, new Thrower<MyException>() {
>     @Override public void convertAndThrow(Throwable e) throws MyException {
>       if (e instanceof InterruptedException) {
>         throw new MyException(e); // library will call interrupt() for you
>       } else if (e instanceof CancellationException) {
>         throw (CancellationException) e;
>       } else {
>         throw new MyException(e); // library will have unwrapped for you
>       }
>     }
>   });
>  }
>
> I do think that such manual conversions can play a role in the overall
> mission of taming Future.get's exceptions.
>
> Thanks for the comments!
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://cs.oswego.edu/pipermail/concurrency-interest/attachments/20110714/398da9c9/attachment.html>


More information about the Concurrency-interest mailing list