Hi,
On 11/23/2015 03:12 PM, Doug Lea wrote:
On 11/23/2015 04:54 AM, Peter Levart wrote:
In CompletableFuture.uniWhenComplete method, the possible exception
thrown from
BiConsumer action is added as suppressed exception to the exception
of the
previous stage. This updated exception is then passed as completion
result to
next stage. When previous stage is appended with more than one
asynchronous
continuation:
...then both secondary exceptions are added as suppressed to the same
primary
exception:
This is not nice for two reasons:
- Throwable::addSuppressed is not thread-safe
- The consumer of the result of one CompletableFuture can see the
exceptional
result being modified as it observes it.
Thanks. The minimal solution is to lock, at least avoiding conflict
among multiple whenCompletes:
! else if (x != ex)
! x.addSuppressed(ex);
--- 771,781 ----
! else if (x != ex) {
! synchronized (x) {
! x.addSuppressed(ex);
! }
! }
This is not as good a solution as your proposal to add Throwable.clone(),
but we should do this until something like clone is in place.
Sorry for confusion. I now noticed that Throwable.addSuppressed() and
getSuppressed() methods are actually synchronized! I don't know how I
missed that...
So above patch is not needed. Even printing uses getSuppressed() that
returns a snapshot. So the only thing remaining is the behavior that
exceptions can change while they are being observed. If this is
acceptable then this whole report of mine is just a bunch of misinformation.
Regards, Peter
When this stage is complete, the given action is invoked with the
result (or
null if none) and the exception (or null if none) of this stage as
arguments.
The returned stage is completed when the action returns. If the
supplied action
itself encounters an exception, then the returned stage exceptionally
completes
with this exception unless this stage also completed exceptionally."
Could specification be tweaked a bit? The last statement leaves it
open to what
actually happens when "this stage also completes exceptionally".
The looseness was intentional. We'd like to improve debuggability of
implementations without strictly promising a particular form in
interfaces.
This is similar to what's done in ForkJoinTask, where we try to
relay exception causes from other threads, but can't promise anything
beyond a plain exception.
-Doug