RFR: jsr166 jdk9 integration wave 2

Peter Levart peter.levart at gmail.com
Mon Nov 23 09:54:39 UTC 2015


On 11/16/2015 10:39 PM, Martin Buchholz wrote:
> Smaller than wave 1, but still large.  Nothing like a looming deadline to
> spur work ...
>
> Oracle folks will need to help with API review.
>
> https://bugs.openjdk.java.net/issues/?jql=(subcomponent%20%3D%20java.util.concurrent)%20AND%20status%20%3D%20%22In%20Progress%22%20ORDER%20BY%20updatedDate%20DESC
> http://cr.openjdk.java.net/~martin/webrevs/openjdk9/jsr166-jdk9-integration/
>
> The primary focus is making jtreg tests more robust and faster.
> It also contains the changes to j.u.c.atomic that Aleksey is waiting for.

Hi,

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:

         CompletableFuture<Void> cf0 = new CompletableFuture<>();

         CompletableFuture<Void> cf1 = cf0.whenCompleteAsync((v, x) -> {
             throw new RuntimeException("Secondary 1");
         });

         CompletableFuture<Void> cf2 = cf0.whenCompleteAsync((v, x) -> {
             throw new RuntimeException("Secondary 2");
         });

         cf0.completeExceptionally(new RuntimeException("Primary"));

         try {
             cf1.get();
         } catch (Exception e) {
             System.err.println("\ncf1 exception:\n");
             e.printStackTrace();
         }

         try {
             cf2.get();
         } catch (Exception e) {
             System.err.println("\ncf2 exception:\n");
             e.printStackTrace();
         }


...then both secondary exceptions are added as suppressed to the same 
primary exception:


cf1 exception:

java.util.concurrent.ExecutionException: java.lang.RuntimeException: Primary
         at 
java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:386)
         at 
java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1948)
         at CFTest.main(CFTest.java:22)
Caused by: java.lang.RuntimeException: Primary
         at CFTest.main(CFTest.java:19)
         Suppressed: java.lang.RuntimeException: Secondary 2
                 at CFTest.lambda$main$1(CFTest.java:16)
                 at 
java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:795)
                 at 
java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:771)
                 at 
java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:478)
                 at 
java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:281)
                 at 
java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1149)
                 at 
java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1985)
                 at 
java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1933)
                 at 
java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157) 

         Suppressed: java.lang.RuntimeException: Secondary 1
                 at CFTest.lambda$main$0(CFTest.java:12)
                 at 
java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:795) 

                 at 
java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:771) 

                 at 
java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:478) 

                 at 
java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:281)
                 at 
java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1149)
                 at 
java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1985)
                 at 
java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1933)
                 at 
java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

cf2 exception:

java.util.concurrent.ExecutionException: java.lang.RuntimeException: Primary
         at 
java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:386)
         at 
java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1948)
         at CFTest.main(CFTest.java:29)
Caused by: java.lang.RuntimeException: Primary
         at CFTest.main(CFTest.java:19)
         Suppressed: java.lang.RuntimeException: Secondary 2
                 at CFTest.lambda$main$1(CFTest.java:16)
                 at 
java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:795)
                 at 
java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:771)
                 at 
java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:478)
                 at 
java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:281)
                 at 
java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1149)
                 at 
java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1985)
                 at 
java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1933)
                 at 
java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
         Suppressed: java.lang.RuntimeException: Secondary 1
                 at CFTest.lambda$main$0(CFTest.java:12)
                 at 
java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:795)
                 at 
java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:771)
                 at 
java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:478)
                 at 
java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:281)
                 at 
java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1149)
                 at 
java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1985)
                 at 
java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1933)
                 at 
java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)


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.


When Chris Purcell reported the issue of discarding the whenComplete 
action exception on concurrency-interest list:

"CompletionStage.whenComplete discards exceptions if both input stage 
and action fail. It would be less surprising if, like try/finally, it 
added the action exception to the input exception as a suppressed 
exception. This can be done safely by cloning the input exception (all 
Throwables are Serializable). I don't think performance should be a 
blocker, as this is a rare edge case, and we are providing a very useful 
service for the cost."

...she suggested to deep-clone the input stage exception before adding 
suppressed exception to it. Each "branch" of continuations would then 
proceed with it's own copy of input-stage exception. This might work 
most of the times, but can fail if input-stage exception references 
non-serializable objects.

The reason for not doing it the other way around which would be more 
natural to forking stages (adding input-stage exception as a suppressed 
exception to the action exception and pass the modified action exception 
as a result of next stage) is the specification of whenCompleteAsync:

"Returns a new CompletionStage with the same result or exception as this 
stage, that executes the given action using this stage's default 
asynchronous execution facility when this stage completes.
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". 
Could this unspecified case be spelled out like this:

... If the supplied action itself encounters an exception, then the 
returned stage exceptionally completes with this exception unless this 
stage also completed exceptionally *in which case the returned stage 
exceptionally completes with the exception thrown from the supplied 
action to which this stage's exception is appended as suppressed exception.
*

Regards, Peter
*
*



More information about the core-libs-dev mailing list