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.
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