<div dir="ltr"><div><span style="font-size:12.8px">I'm still holding out for option C: none of the above.  (Also I want access to the voter database!)</span></div><div><span style="font-size:12.8px"><br></span></div><div><span style="font-size:12.8px">The concerns that I didn't express very well in my survey response are:</span></div><div><span style="font-size:12.8px"><br></span></div><div><span style="font-size:12.8px">1. That exception1 is not "suppressed" because it has already been delivered to user code.</span></div><div><span style="font-size:12.8px"><br></span></div><div><span style="font-size:12.8px">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.</span></div><span style="font-size:12.8px"><div><span style="font-size:12.8px"><br></span></div><div><span style="font-size:12.8px">2. The completion handler receiving exception1 may not want it to reach the next stage, even via getSuppressedExceptions</span><span style="font-size:12.8px">.</span></div><div><span style="font-size:12.8px"><br></span></div><div>--Joe</div></span></div><div class="gmail_extra"><br><div class="gmail_quote">On Sat, Dec 19, 2015 at 5:08 AM, Doug Lea <span dir="ltr"><<a href="mailto:dl@cs.oswego.edu" target="_blank">dl@cs.oswego.edu</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><br>
Thanks to the 71 people who answered the survey. The majority (52)<br>
voted for the second option. To recap, here's the (fixed) question:<br>
<br>
  CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> {<br>
     if (true)<br>
        throw new FirstException();<br>
     else<br>
        return "A"; });<br>
<br>
  CompletableFuture<String> f2 = f1.whenComplete((result, exception) -> {<br>
     if (exception != null) {<br>
        if (true)<br>
           throw new SecondException();<br>
         else<br>
           return;<br>
     }<br>
     else<br>
        return; });<br>
<br>
(where "true" stands in for some error condition).<br>
<br>
What should the completed value of f2 be?<br>
<br>
19: The FirstException, with the SecondException as suppressed exception<br>
52: The SecondException, with the FirstException as suppressed exception<br>
<br>
The vote was closer among the 29 people who filled out the optional<br>
rationale, but still favoring the second.<br>
<br>
One consideration differentiating votes is whether SecondException was<br>
viewed as intentional versus the result of a programming error or<br>
cleanup artifact. Regardless of vote outcome, throwing an exception in an<br>
exception handler (in CompletableFutures or elsewhere) is likely to<br>
surprise some users.<br>
<br>
The most detailed rationale, by Tim Peierls, was<br>
long enough that he had to place it elsewhere:<br>
  <a href="https://gist.github.com/Tembrel/68d5670bf37824d55078" rel="noreferrer" target="_blank">https://gist.github.com/Tembrel/68d5670bf37824d55078</a><br>
<br>
Others are below.<br>
<br>
...<br>
<br>
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.<br>
<br>
...<br>
<br>
the most recent exception should be first, with prior ones nested<br>
<br>
...<br>
<br>
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.<br>
<br>
...<br>
<br>
the first exception was "handled" by the whenComplete, but not successfully<br>
<br>
...<br>
<br>
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.<br>
<br>
...<br>
<br>
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.<br>
<br>
...<br>
<br>
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.<br>
<br>
...<br>
<br>
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.<br>
<br>
...<br>
<br>
since I specifically handle FirstException and decide to throw SecondException that would be the one that I expect at the end<br>
<br>
...<br>
<br>
f2 was supposed to handle exception from f1, but failed, so we throw SecondException. To not lose data, we suppress FirstException<br>
<br>
...<br>
<br>
First exception is likely to be the cause of a whole issue.<br>
<br>
...<br>
<br>
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.<br>
<br>
...<br>
<br>
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.<br>
<br>
...<br>
<br>
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.<br>
<br>
...<br>
It should be isomorphic to throwing an exception in a catch block.<br>
<br>
...<br>
<br>
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.<br>
<br>
...<br>
<br>
SecondException is the one that is last thrown.<br>
<br>
...<br>
<br>
For f2, SecondException is the primary exception (at the top of the ladder), and everything else must trail behind in the context of historicity<br>
<br>
...<br>
<br>
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<br>
<br>
...<br>
<br>
For consistency as an async version of a try-catch-finally block. Here "then" == try; "exceptionally" == catch; "when"/"handle" == finally.<br>
<br>
...<br>
<br>
[Option 1] mirrors try-with-resources and (arguably) is how try-finally should have been specified when both the try and finally blocks throw.<br>
<br>
...<br>
<br>
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.<br>
<br>
...<br>
<br>
Order of execution makes me feel like an exception from f1 should be the root, and f2 be the suppressed exception.<br>
<br>
...<br>
<br>
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.<br>
<br>
...<br>
<br>
No time travelling to change history!<br>
<br>
...<br>
<br>
When ever an in-flight exception gets replaced it should become a suppressed exception of the new exception.<br>
<br>
...<br>
<br>
try-with-resources will add exceptions from close() as suppressed exceptions.<div class="HOEnZb"><div class="h5"><br>
<br>
<br>
<br>
<br>
<br>
<br>
_______________________________________________<br>
Concurrency-interest mailing list<br>
<a href="mailto:Concurrency-interest@cs.oswego.edu" target="_blank">Concurrency-interest@cs.oswego.edu</a><br>
<a href="http://cs.oswego.edu/mailman/listinfo/concurrency-interest" rel="noreferrer" target="_blank">http://cs.oswego.edu/mailman/listinfo/concurrency-interest</a><br>
</div></div></blockquote></div><br></div>