[concurrency-interest] CompletableFuture.whenComplete survey

Joe Bowbeer joe.bowbeer at gmail.com
Sat Dec 19 08:40:27 EST 2015


I'm still holding out for option C: none of the above.  (Also I want access
to the voter database!)

The concerns that I didn't express very well in my survey response are:

1. That exception1 is not "suppressed" because it has already been
delivered to user code.

I think the comparison with ARM try-with-resources breaks down because in
try-with-resources the exception *is* suppressed by the framework, and
would otherwise go unreported.

2. The completion handler receiving exception1 may not want it to reach the
next stage, even via getSuppressedExceptions.

--Joe

On Sat, Dec 19, 2015 at 5:08 AM, Doug Lea <dl at cs.oswego.edu> wrote:

>
> Thanks to the 71 people who answered the survey. The majority (52)
> voted for the second option. To recap, here's the (fixed) question:
>
>   CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> {
>      if (true)
>         throw new FirstException();
>      else
>         return "A"; });
>
>   CompletableFuture<String> f2 = f1.whenComplete((result, exception) -> {
>      if (exception != null) {
>         if (true)
>            throw new SecondException();
>          else
>            return;
>      }
>      else
>         return; });
>
> (where "true" stands in for some error condition).
>
> What should the completed value of f2 be?
>
> 19: The FirstException, with the SecondException as suppressed exception
> 52: The SecondException, with the FirstException as suppressed exception
>
> The vote was closer among the 29 people who filled out the optional
> rationale, but still favoring the second.
>
> One consideration differentiating votes is whether SecondException was
> viewed as intentional versus the result of a programming error or
> cleanup artifact. Regardless of vote outcome, throwing an exception in an
> exception handler (in CompletableFutures or elsewhere) is likely to
> surprise some users.
>
> The most detailed rationale, by Tim Peierls, was
> long enough that he had to place it elsewhere:
>   https://gist.github.com/Tembrel/68d5670bf37824d55078
>
> Others are below.
>
> ...
>
> to me, it's as if the lambda is wrapped in a try-catch, with the `if
> (exception != null)` block as the catch. As this doesn't rethrow the
> FirstException, f2 should contain the thrown SecondException.
>
> ...
>
> the most recent exception should be first, with prior ones nested
>
> ...
>
> whenComplete shouldn't allow overwriting a normal result with a different
> normal result despite what the example code implies. Nor should it allow
> replacing an exceptional result with a different exceptional result, but
> going with the second option would be same as specifying just that, with an
> added wrinkle that the replacement exception is modified to suppress the
> original if it doesn't do that already.
>
> ...
>
> the first exception was "handled" by the whenComplete, but not successfully
>
> ...
>
> By using whenComplete, the user is declaring an expectation that
> FirstException may occur and are saying "I'll handle it." They haven't
> declared they will handle SecondException, so that one seems more likely
> (than the first) to be a programming error, and more important to call out.
>
> ...
>
> per "then the returned stage exceptionally completes with this exception
> unless this stage also completed exceptionally." unless is the key word
> indicating f1 exception has primacy.
>
> ...
>
> This matches the current behavior (aside from the additional suppressed
> exception, of course). It also matches try-with-resources, the other main
> case for suppressed exceptions. (It consequently doesn't match try-finally,
> but we can match only one of the two, and try-with-resources, besides being
> the one that uses suppressed exceptions, is newer and (debatably) a
> replacement for most existing try-finally blocks.) But I admit that the
> try-with-resources and try-finally analogies aren't perfect for
> whenComplete(): whenComplete() has access to the result, but the
> try-with-resources close() implementation and the try-finally's finally do
> not. If whenComplete() is intended to handle other use cases, then those
> use cases may have different requirements.
>
> ...
>
> As long as application code has the opportunity to be aware of the first
> exception and take action, it's not suppressed. Suppressing the second
> exception would mean no application code has the opportunity to even be
> aware of it.
>
> ...
>
> since I specifically handle FirstException and decide to throw
> SecondException that would be the one that I expect at the end
>
> ...
>
> f2 was supposed to handle exception from f1, but failed, so we throw
> SecondException. To not lose data, we suppress FirstException
>
> ...
>
> First exception is likely to be the cause of a whole issue.
>
> ...
>
> 1. The contract of F2 is throw SecondException, not FirstException. 2. The
> implementor is knowingly suppressing FirstException, similar supplying the
> caused by exception when wrapping a thrown exception.
>
> ...
>
> It should be treated the same way, when FirstException occurred in a
> try-block and SecondException occurred in finally block. I think the
> try-exception is the one which is thrown and the finally-exception is added
> to it as suppressed.
>
> ...
>
> It is the least change to the current semantics; it matches the behaviour
> of try-with-resources; it lets you write a stage that releases resources
> without worrying about the exceptional path of the main code.
>
> ...
> It should be isomorphic to throwing an exception in a catch block.
>
> ...
>
> The second exception is further down the line and one can think of it as
> the transformation of the first exception, i.e., closest failure first
> (similar to how printStackTrace prints the closest method first, not the
> Thread's starting method). The third option would be to introduce some
> CompositeException that will get both exceptions suppressed, in timely
> order. Besides, a fluent java.util.concurrent.Flow library does this when
> lambdas crash on the onError path.
>
> ...
>
> SecondException is the one that is last thrown.
>
> ...
>
> For f2, SecondException is the primary exception (at the top of the
> ladder), and everything else must trail behind in the context of historicity
>
> ...
>
> I can see reasons for both options, but in the end, I think that the
> caller of oncomplete passes in a lambda which may have a known and well
> defined set of throws, thus they are entitled to expect that f2 will only
> have values that can be thrown by the lambda
>
> ...
>
> For consistency as an async version of a try-catch-finally block. Here
> "then" == try; "exceptionally" == catch; "when"/"handle" == finally.
>
> ...
>
> [Option 1] mirrors try-with-resources and (arguably) is how try-finally
> should have been specified when both the try and finally blocks throw.
>
> ...
>
> At first glance, I was viewing exception2 as the suppressed one, but then
> I wondered what happens in a chain of exceptional completions. Do
> suppressed exceptions chain? That is, what happens in a chain of
> exceptional completions? Is there a way to draw an analogy to a nesting of
> ARM statements? On second or third thought, I don't see the need for
> suppressed exceptions here, and worry a little bit about creating one
> automatically. I say there is not a real suppressed exception in this case
> because the first exception is "explicit" in the completion arguments.
> However, there is a chance that the earlier exception won't be handled by
> the completion code, and will be lost. And so, as a nicety (B) the first
> exception could be added as a suppressed exception of exception2.
>
> ...
>
> Order of execution makes me feel like an exception from f1 should be the
> root, and f2 be the suppressed exception.
>
> ...
>
> The implementor of f2 is explicitly handling the exception from f1 and
> throwing a new one. That one is the one that matters to the client of f2,
> at least from f2's perspective.
>
> ...
>
> No time travelling to change history!
>
> ...
>
> When ever an in-flight exception gets replaced it should become a
> suppressed exception of the new exception.
>
> ...
>
> try-with-resources will add exceptions from close() as suppressed
> exceptions.
>
>
>
>
>
>
>
> _______________________________________________
> Concurrency-interest mailing list
> Concurrency-interest at cs.oswego.edu
> http://cs.oswego.edu/mailman/listinfo/concurrency-interest
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://cs.oswego.edu/pipermail/concurrency-interest/attachments/20151219/b8352379/attachment-0001.html>


More information about the Concurrency-interest mailing list