# HG changeset patch # User dl # Date 1474661654 25200 # Node ID 0dfc0bc2196c10996b21ab3bda555261b46019d6 # Parent 186dce57546326526f4c598db7d97797de531575 8166465: CompletableFuture.minimalCompletionStage().toCompletableFuture() should be non-minimal Reviewed-by: martin, chegar, shade diff -r 186dce575463 -r 0dfc0bc2196c jdk/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java --- a/jdk/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java Fri Sep 23 12:08:38 2016 +0300 +++ b/jdk/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java Fri Sep 23 13:14:14 2016 -0700 @@ -2559,6 +2559,13 @@ * exceptionally with a CompletionException with this exception as * cause. * + *

Unless overridden by a subclass, a new non-minimal + * CompletableFuture with all methods available can be obtained from + * a minimal CompletionStage via {@link #toCompletableFuture()}. + * For example, completion of a minimal stage can be awaited by + * + *

 {@code minimalStage.toCompletableFuture().join(); }
+ * * @return the new CompletionStage * @since 9 */ @@ -2853,6 +2860,16 @@ @Override public CompletableFuture completeOnTimeout (T value, long timeout, TimeUnit unit) { throw new UnsupportedOperationException(); } + @Override public CompletableFuture toCompletableFuture() { + Object r; + if ((r = result) != null) + return new CompletableFuture(encodeRelay(r)); + else { + CompletableFuture d = new CompletableFuture<>(); + unipush(new UniRelay(d, this)); + return d; + } + } } // VarHandle mechanics diff -r 186dce575463 -r 0dfc0bc2196c jdk/test/java/util/concurrent/tck/CompletableFutureTest.java --- a/jdk/test/java/util/concurrent/tck/CompletableFutureTest.java Fri Sep 23 12:08:38 2016 +0300 +++ b/jdk/test/java/util/concurrent/tck/CompletableFutureTest.java Fri Sep 23 13:14:14 2016 -0700 @@ -388,7 +388,7 @@ checkCompletedNormally(f, "test"); } - abstract class CheckedAction { + abstract static class CheckedAction { int invocationCount = 0; final ExecutionMode m; CheckedAction(ExecutionMode m) { this.m = m; } @@ -400,7 +400,7 @@ void assertInvoked() { assertEquals(1, invocationCount); } } - abstract class CheckedIntegerAction extends CheckedAction { + abstract static class CheckedIntegerAction extends CheckedAction { Integer value; CheckedIntegerAction(ExecutionMode m) { super(m); } void assertValue(Integer expected) { @@ -409,7 +409,7 @@ } } - class IntegerSupplier extends CheckedAction + static class IntegerSupplier extends CheckedAction implements Supplier { final Integer value; @@ -428,7 +428,7 @@ return (x == null) ? null : x + 1; } - class NoopConsumer extends CheckedIntegerAction + static class NoopConsumer extends CheckedIntegerAction implements Consumer { NoopConsumer(ExecutionMode m) { super(m); } @@ -438,7 +438,7 @@ } } - class IncFunction extends CheckedIntegerAction + static class IncFunction extends CheckedIntegerAction implements Function { IncFunction(ExecutionMode m) { super(m); } @@ -456,7 +456,7 @@ - ((y == null) ? 99 : y.intValue()); } - class SubtractAction extends CheckedIntegerAction + static class SubtractAction extends CheckedIntegerAction implements BiConsumer { SubtractAction(ExecutionMode m) { super(m); } @@ -466,7 +466,7 @@ } } - class SubtractFunction extends CheckedIntegerAction + static class SubtractFunction extends CheckedIntegerAction implements BiFunction { SubtractFunction(ExecutionMode m) { super(m); } @@ -476,14 +476,14 @@ } } - class Noop extends CheckedAction implements Runnable { + static class Noop extends CheckedAction implements Runnable { Noop(ExecutionMode m) { super(m); } public void run() { invoked(); } } - class FailingSupplier extends CheckedAction + static class FailingSupplier extends CheckedAction implements Supplier { final CFException ex; @@ -494,7 +494,7 @@ } } - class FailingConsumer extends CheckedIntegerAction + static class FailingConsumer extends CheckedIntegerAction implements Consumer { final CFException ex; @@ -506,7 +506,7 @@ } } - class FailingBiConsumer extends CheckedIntegerAction + static class FailingBiConsumer extends CheckedIntegerAction implements BiConsumer { final CFException ex; @@ -518,7 +518,7 @@ } } - class FailingFunction extends CheckedIntegerAction + static class FailingFunction extends CheckedIntegerAction implements Function { final CFException ex; @@ -530,7 +530,7 @@ } } - class FailingBiFunction extends CheckedIntegerAction + static class FailingBiFunction extends CheckedIntegerAction implements BiFunction { final CFException ex; @@ -542,7 +542,7 @@ } } - class FailingRunnable extends CheckedAction implements Runnable { + static class FailingRunnable extends CheckedAction implements Runnable { final CFException ex; FailingRunnable(ExecutionMode m) { super(m); ex = new CFException(); } public void run() { @@ -551,7 +551,7 @@ } } - class CompletableFutureInc extends CheckedIntegerAction + static class CompletableFutureInc extends CheckedIntegerAction implements Function> { CompletableFutureInc(ExecutionMode m) { super(m); } @@ -564,7 +564,7 @@ } } - class FailingCompletableFutureFunction extends CheckedIntegerAction + static class FailingCompletableFutureFunction extends CheckedIntegerAction implements Function> { final CFException ex; @@ -3604,29 +3604,53 @@ * copy returns a CompletableFuture that is completed normally, * with the same value, when source is. */ - public void testCopy() { + public void testCopy_normalCompletion() { + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) assertTrue(f.complete(v1)); CompletableFuture g = f.copy(); - checkIncomplete(f); - checkIncomplete(g); - f.complete(1); - checkCompletedNormally(f, 1); - checkCompletedNormally(g, 1); - } + if (createIncomplete) { + checkIncomplete(f); + checkIncomplete(g); + assertTrue(f.complete(v1)); + } + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v1); + }} /** * copy returns a CompletableFuture that is completed exceptionally * when source is. */ - public void testCopy2() { + public void testCopy_exceptionalCompletion() { + for (boolean createIncomplete : new boolean[] { true, false }) + { + CFException ex = new CFException(); CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) f.completeExceptionally(ex); CompletableFuture g = f.copy(); - checkIncomplete(f); - checkIncomplete(g); - CFException ex = new CFException(); - f.completeExceptionally(ex); + if (createIncomplete) { + checkIncomplete(f); + checkIncomplete(g); + f.completeExceptionally(ex); + } checkCompletedExceptionally(f, ex); checkCompletedWithWrappedException(g, ex); + }} + + /** + * Completion of a copy does not complete its source. + */ + public void testCopy_oneWayPropagation() { + CompletableFuture f = new CompletableFuture<>(); + assertTrue(f.copy().complete(1)); + assertTrue(f.copy().complete(null)); + assertTrue(f.copy().cancel(true)); + assertTrue(f.copy().cancel(false)); + assertTrue(f.copy().completeExceptionally(new CFException())); + checkIncomplete(f); } /** @@ -3991,7 +4015,10 @@ .collect(Collectors.toList()); List> stages = new ArrayList<>(); - stages.add(new CompletableFuture().minimalCompletionStage()); + CompletionStage min = + new CompletableFuture().minimalCompletionStage(); + stages.add(min); + stages.add(min.thenApply(x -> x)); stages.add(CompletableFuture.completedStage(1)); stages.add(CompletableFuture.failedStage(new CFException())); @@ -4027,6 +4054,131 @@ throw new Error("Methods did not throw UOE: " + bugs); } + /** + * minimalStage.toCompletableFuture() returns a CompletableFuture that + * is completed normally, with the same value, when source is. + */ + public void testMinimalCompletionStage_toCompletableFuture_normalCompletion() { + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = new CompletableFuture<>(); + CompletionStage minimal = f.minimalCompletionStage(); + if (!createIncomplete) assertTrue(f.complete(v1)); + CompletableFuture g = minimal.toCompletableFuture(); + if (createIncomplete) { + checkIncomplete(f); + checkIncomplete(g); + assertTrue(f.complete(v1)); + } + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v1); + }} + + /** + * minimalStage.toCompletableFuture() returns a CompletableFuture that + * is completed exceptionally when source is. + */ + public void testMinimalCompletionStage_toCompletableFuture_exceptionalCompletion() { + for (boolean createIncomplete : new boolean[] { true, false }) + { + CFException ex = new CFException(); + CompletableFuture f = new CompletableFuture<>(); + CompletionStage minimal = f.minimalCompletionStage(); + if (!createIncomplete) f.completeExceptionally(ex); + CompletableFuture g = minimal.toCompletableFuture(); + if (createIncomplete) { + checkIncomplete(f); + checkIncomplete(g); + f.completeExceptionally(ex); + } + checkCompletedExceptionally(f, ex); + checkCompletedWithWrappedException(g, ex); + }} + + /** + * minimalStage.toCompletableFuture() gives mutable CompletableFuture + */ + public void testMinimalCompletionStage_toCompletableFuture_mutable() { + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = new CompletableFuture<>(); + CompletionStage minimal = f.minimalCompletionStage(); + CompletableFuture g = minimal.toCompletableFuture(); + assertTrue(g.complete(v1)); + checkCompletedNormally(g, v1); + checkIncomplete(f); + checkIncomplete(minimal.toCompletableFuture()); + }} + + /** + * minimalStage.toCompletableFuture().join() awaits completion + */ + public void testMinimalCompletionStage_toCompletableFuture_join() throws Exception { + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) assertTrue(f.complete(v1)); + CompletionStage minimal = f.minimalCompletionStage(); + if (createIncomplete) assertTrue(f.complete(v1)); + assertEquals(v1, minimal.toCompletableFuture().join()); + assertEquals(v1, minimal.toCompletableFuture().get()); + checkCompletedNormally(minimal.toCompletableFuture(), v1); + }} + + /** + * Completion of a toCompletableFuture copy of a minimal stage + * does not complete its source. + */ + public void testMinimalCompletionStage_toCompletableFuture_oneWayPropagation() { + CompletableFuture f = new CompletableFuture<>(); + CompletionStage g = f.minimalCompletionStage(); + assertTrue(g.toCompletableFuture().complete(1)); + assertTrue(g.toCompletableFuture().complete(null)); + assertTrue(g.toCompletableFuture().cancel(true)); + assertTrue(g.toCompletableFuture().cancel(false)); + assertTrue(g.toCompletableFuture().completeExceptionally(new CFException())); + checkIncomplete(g.toCompletableFuture()); + f.complete(1); + checkCompletedNormally(g.toCompletableFuture(), 1); + } + + /** Demo utility method for external reliable toCompletableFuture */ + static CompletableFuture toCompletableFuture(CompletionStage stage) { + CompletableFuture f = new CompletableFuture<>(); + stage.handle((T t, Throwable ex) -> { + if (ex != null) f.completeExceptionally(ex); + else f.complete(t); + return null; + }); + return f; + } + + /** Demo utility method to join a CompletionStage */ + static T join(CompletionStage stage) { + return toCompletableFuture(stage).join(); + } + + /** + * Joining a minimal stage "by hand" works + */ + public void testMinimalCompletionStage_join_by_hand() { + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = new CompletableFuture<>(); + CompletionStage minimal = f.minimalCompletionStage(); + CompletableFuture g = new CompletableFuture<>(); + if (!createIncomplete) assertTrue(f.complete(v1)); + minimal.thenAccept((x) -> g.complete(x)); + if (createIncomplete) assertTrue(f.complete(v1)); + g.join(); + checkCompletedNormally(g, v1); + checkCompletedNormally(f, v1); + assertEquals(v1, join(minimal)); + }} + static class Monad { static class ZeroException extends RuntimeException { public ZeroException() { super("monadic zero"); } @@ -4317,6 +4469,22 @@ assertTrue(neverCompleted.thenRun(() -> {}).cancel(true)); } + /** + * Checks for garbage retention when MinimalStage.toCompletableFuture() + * is invoked many times. + * 8161600: Garbage retention when source CompletableFutures are never completed + * + * As of 2016-07, fails with OOME: + * ant -Dvmoptions=-Xmx8m -Djsr166.expensiveTests=true -Djsr166.tckTestClass=CompletableFutureTest -Djsr166.methodFilter=testToCompletableFutureGarbageRetention tck + */ + public void testToCompletableFutureGarbageRetention() throws Throwable { + final int n = expensiveTests ? 900_000 : 10; + CompletableFuture neverCompleted = new CompletableFuture<>(); + CompletionStage minimal = neverCompleted.minimalCompletionStage(); + for (int i = 0; i < n; i++) + assertTrue(minimal.toCompletableFuture().cancel(true)); + } + // static U join(CompletionStage stage) { // CompletableFuture f = new CompletableFuture<>(); // stage.whenComplete((v, ex) -> {