--- jsr166/src/test/tck/CompletableFutureTest.java 2015/11/15 18:24:25 1.131 +++ jsr166/src/test/tck/CompletableFutureTest.java 2016/06/26 15:46:22 1.148 @@ -876,6 +876,10 @@ public class CompletableFutureTest exten assertEquals(1, a.get()); }} + /** + * If an "exceptionally action" throws an exception, it completes + * exceptionally with that exception + */ public void testExceptionally_exceptionalCompletionActionFailed() { for (boolean createIncomplete : new boolean[] { true, false }) { @@ -894,6 +898,7 @@ public class CompletableFutureTest exten if (createIncomplete) f.completeExceptionally(ex1); checkCompletedWithWrappedException(g, ex2); + checkCompletedExceptionally(f, ex1); assertEquals(1, a.get()); }} @@ -911,9 +916,9 @@ public class CompletableFutureTest exten if (!createIncomplete) assertTrue(f.complete(v1)); final CompletableFuture g = m.whenComplete (f, - (Integer x, Throwable t) -> { + (Integer result, Throwable t) -> { m.checkExecutionMode(); - threadAssertSame(x, v1); + threadAssertSame(result, v1); threadAssertNull(t); a.getAndIncrement(); }); @@ -938,9 +943,9 @@ public class CompletableFutureTest exten if (!createIncomplete) f.completeExceptionally(ex); final CompletableFuture g = m.whenComplete (f, - (Integer x, Throwable t) -> { + (Integer result, Throwable t) -> { m.checkExecutionMode(); - threadAssertNull(x); + threadAssertNull(result); threadAssertSame(t, ex); a.getAndIncrement(); }); @@ -965,9 +970,9 @@ public class CompletableFutureTest exten if (!createIncomplete) assertTrue(f.cancel(mayInterruptIfRunning)); final CompletableFuture g = m.whenComplete (f, - (Integer x, Throwable t) -> { + (Integer result, Throwable t) -> { m.checkExecutionMode(); - threadAssertNull(x); + threadAssertNull(result); threadAssertTrue(t instanceof CancellationException); a.getAndIncrement(); }); @@ -982,7 +987,7 @@ public class CompletableFutureTest exten * If a whenComplete action throws an exception when triggered by * a normal completion, it completes exceptionally */ - public void testWhenComplete_actionFailed() { + public void testWhenComplete_sourceCompletedNormallyActionFailed() { for (boolean createIncomplete : new boolean[] { true, false }) for (ExecutionMode m : ExecutionMode.values()) for (Integer v1 : new Integer[] { 1, null }) @@ -993,9 +998,9 @@ public class CompletableFutureTest exten if (!createIncomplete) assertTrue(f.complete(v1)); final CompletableFuture g = m.whenComplete (f, - (Integer x, Throwable t) -> { + (Integer result, Throwable t) -> { m.checkExecutionMode(); - threadAssertSame(x, v1); + threadAssertSame(result, v1); threadAssertNull(t); a.getAndIncrement(); throw ex; @@ -1010,9 +1015,9 @@ public class CompletableFutureTest exten /** * If a whenComplete action throws an exception when triggered by * a source completion that also throws an exception, the source - * exception takes precedence. + * exception takes precedence (unlike handle) */ - public void testWhenComplete_actionFailedSourceFailed() { + public void testWhenComplete_sourceFailedActionFailed() { for (boolean createIncomplete : new boolean[] { true, false }) for (ExecutionMode m : ExecutionMode.values()) { @@ -1024,10 +1029,10 @@ public class CompletableFutureTest exten if (!createIncomplete) f.completeExceptionally(ex1); final CompletableFuture g = m.whenComplete (f, - (Integer x, Throwable t) -> { + (Integer result, Throwable t) -> { m.checkExecutionMode(); threadAssertSame(t, ex1); - threadAssertNull(x); + threadAssertNull(result); a.getAndIncrement(); throw ex2; }); @@ -1035,6 +1040,10 @@ public class CompletableFutureTest exten checkCompletedWithWrappedException(g, ex1); checkCompletedExceptionally(f, ex1); + if (testImplementationDetails) { + assertEquals(1, ex1.getSuppressed().length); + assertSame(ex2, ex1.getSuppressed()[0]); + } assertEquals(1, a.get()); }} @@ -1052,9 +1061,9 @@ public class CompletableFutureTest exten if (!createIncomplete) assertTrue(f.complete(v1)); final CompletableFuture g = m.handle (f, - (Integer x, Throwable t) -> { + (Integer result, Throwable t) -> { m.checkExecutionMode(); - threadAssertSame(x, v1); + threadAssertSame(result, v1); threadAssertNull(t); a.getAndIncrement(); return inc(v1); @@ -1081,9 +1090,9 @@ public class CompletableFutureTest exten if (!createIncomplete) f.completeExceptionally(ex); final CompletableFuture g = m.handle (f, - (Integer x, Throwable t) -> { + (Integer result, Throwable t) -> { m.checkExecutionMode(); - threadAssertNull(x); + threadAssertNull(result); threadAssertSame(t, ex); a.getAndIncrement(); return v1; @@ -1110,9 +1119,9 @@ public class CompletableFutureTest exten if (!createIncomplete) assertTrue(f.cancel(mayInterruptIfRunning)); final CompletableFuture g = m.handle (f, - (Integer x, Throwable t) -> { + (Integer result, Throwable t) -> { m.checkExecutionMode(); - threadAssertNull(x); + threadAssertNull(result); threadAssertTrue(t instanceof CancellationException); a.getAndIncrement(); return v1; @@ -1125,55 +1134,62 @@ public class CompletableFutureTest exten }} /** - * handle result completes exceptionally if action does + * If a "handle action" throws an exception when triggered by + * a normal completion, it completes exceptionally */ - public void testHandle_sourceFailedActionFailed() { + public void testHandle_sourceCompletedNormallyActionFailed() { for (ExecutionMode m : ExecutionMode.values()) for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) { final CompletableFuture f = new CompletableFuture<>(); final AtomicInteger a = new AtomicInteger(0); - final CFException ex1 = new CFException(); - final CFException ex2 = new CFException(); - if (!createIncomplete) f.completeExceptionally(ex1); + final CFException ex = new CFException(); + if (!createIncomplete) assertTrue(f.complete(v1)); final CompletableFuture g = m.handle (f, - (Integer x, Throwable t) -> { + (Integer result, Throwable t) -> { m.checkExecutionMode(); - threadAssertNull(x); - threadAssertSame(ex1, t); + threadAssertSame(result, v1); + threadAssertNull(t); a.getAndIncrement(); - throw ex2; + throw ex; }); - if (createIncomplete) f.completeExceptionally(ex1); + if (createIncomplete) assertTrue(f.complete(v1)); - checkCompletedWithWrappedException(g, ex2); - checkCompletedExceptionally(f, ex1); + checkCompletedWithWrappedException(g, ex); + checkCompletedNormally(f, v1); assertEquals(1, a.get()); }} - public void testHandle_sourceCompletedNormallyActionFailed() { - for (ExecutionMode m : ExecutionMode.values()) + /** + * If a "handle action" throws an exception when triggered by + * a source completion that also throws an exception, the action + * exception takes precedence (unlike whenComplete) + */ + public void testHandle_sourceFailedActionFailed() { for (boolean createIncomplete : new boolean[] { true, false }) - for (Integer v1 : new Integer[] { 1, null }) + for (ExecutionMode m : ExecutionMode.values()) { - final CompletableFuture f = new CompletableFuture<>(); final AtomicInteger a = new AtomicInteger(0); - final CFException ex = new CFException(); - if (!createIncomplete) assertTrue(f.complete(v1)); + final CFException ex1 = new CFException(); + final CFException ex2 = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + + if (!createIncomplete) f.completeExceptionally(ex1); final CompletableFuture g = m.handle (f, - (Integer x, Throwable t) -> { + (Integer result, Throwable t) -> { m.checkExecutionMode(); - threadAssertSame(x, v1); - threadAssertNull(t); + threadAssertNull(result); + threadAssertSame(ex1, t); a.getAndIncrement(); - throw ex; + throw ex2; }); - if (createIncomplete) assertTrue(f.complete(v1)); + if (createIncomplete) f.completeExceptionally(ex1); - checkCompletedWithWrappedException(g, ex); - checkCompletedNormally(f, v1); + checkCompletedWithWrappedException(g, ex2); + checkCompletedExceptionally(f, ex1); assertEquals(1, a.get()); }} @@ -3279,7 +3295,7 @@ public class CompletableFutureTest exten () -> f.obtrudeException(null), () -> CompletableFuture.delayedExecutor(1L, SECONDS, null), - () -> CompletableFuture.delayedExecutor(1L, null, new ThreadExecutor()), + () -> CompletableFuture.delayedExecutor(1L, null, exec), () -> CompletableFuture.delayedExecutor(1L, null), () -> f.orTimeout(1L, null), @@ -3509,7 +3525,7 @@ public class CompletableFutureTest exten long timeoutMillis = timeoutMillis(); CompletableFuture f = new CompletableFuture<>(); long startTime = System.nanoTime(); - f.orTimeout(timeoutMillis, MILLISECONDS); + assertSame(f, f.orTimeout(timeoutMillis, MILLISECONDS)); checkCompletedWithTimeoutException(f); assertTrue(millisElapsedSince(startTime) >= timeoutMillis); } @@ -3524,8 +3540,8 @@ public class CompletableFutureTest exten CompletableFuture g = new CompletableFuture<>(); long startTime = System.nanoTime(); f.complete(v1); - f.orTimeout(LONG_DELAY_MS, MILLISECONDS); - g.orTimeout(LONG_DELAY_MS, MILLISECONDS); + assertSame(f, f.orTimeout(LONG_DELAY_MS, MILLISECONDS)); + assertSame(g, g.orTimeout(LONG_DELAY_MS, MILLISECONDS)); g.complete(v1); checkCompletedNormally(f, v1); checkCompletedNormally(g, v1); @@ -3540,11 +3556,14 @@ public class CompletableFutureTest exten () -> testCompleteOnTimeout_timesOut(null)); } + /** + * completeOnTimeout completes with given value if not complete + */ public void testCompleteOnTimeout_timesOut(Integer v) { long timeoutMillis = timeoutMillis(); CompletableFuture f = new CompletableFuture<>(); long startTime = System.nanoTime(); - f.completeOnTimeout(v, timeoutMillis, MILLISECONDS); + assertSame(f, f.completeOnTimeout(v, timeoutMillis, MILLISECONDS)); assertSame(v, f.join()); assertTrue(millisElapsedSince(startTime) >= timeoutMillis); f.complete(99); // should have no effect @@ -3561,8 +3580,8 @@ public class CompletableFutureTest exten CompletableFuture g = new CompletableFuture<>(); long startTime = System.nanoTime(); f.complete(v1); - f.completeOnTimeout(-1, LONG_DELAY_MS, MILLISECONDS); - g.completeOnTimeout(-1, LONG_DELAY_MS, MILLISECONDS); + assertSame(f, f.completeOnTimeout(-1, LONG_DELAY_MS, MILLISECONDS)); + assertSame(g, g.completeOnTimeout(-1, LONG_DELAY_MS, MILLISECONDS)); g.complete(v1); checkCompletedNormally(f, v1); checkCompletedNormally(g, v1); @@ -3613,12 +3632,25 @@ public class CompletableFutureTest exten //--- tests of implementation details; not part of official tck --- Object resultOf(CompletableFuture f) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + try { + System.setSecurityManager(null); + } catch (SecurityException giveUp) { + return "Reflection not available"; + } + } + try { java.lang.reflect.Field resultField = CompletableFuture.class.getDeclaredField("result"); resultField.setAccessible(true); return resultField.get(f); - } catch (Throwable t) { throw new AssertionError(t); } + } catch (Throwable t) { + throw new AssertionError(t); + } finally { + if (sm != null) System.setSecurityManager(sm); + } } public void testExceptionPropagationReusesResultObject() { @@ -3641,15 +3673,20 @@ public class CompletableFutureTest exten funs.add((y) -> m.applyToEither(y, incomplete, new IncFunction(m))); funs.add((y) -> m.runAfterBoth(y, v42, new Noop(m))); + funs.add((y) -> m.runAfterBoth(v42, y, new Noop(m))); funs.add((y) -> m.thenAcceptBoth(y, v42, new SubtractAction(m))); + funs.add((y) -> m.thenAcceptBoth(v42, y, new SubtractAction(m))); funs.add((y) -> m.thenCombine(y, v42, new SubtractFunction(m))); + funs.add((y) -> m.thenCombine(v42, y, new SubtractFunction(m))); - funs.add((y) -> m.whenComplete(y, (Integer x, Throwable t) -> {})); + funs.add((y) -> m.whenComplete(y, (Integer r, Throwable t) -> {})); funs.add((y) -> m.thenCompose(y, new CompletableFutureInc(m))); funs.add((y) -> CompletableFuture.allOf(new CompletableFuture[] {y, v42})); + funs.add((y) -> CompletableFuture.allOf(new CompletableFuture[] {v42, y})); funs.add((y) -> CompletableFuture.anyOf(new CompletableFuture[] {y, incomplete})); + funs.add((y) -> CompletableFuture.anyOf(new CompletableFuture[] {incomplete, y})); for (Function, CompletableFuture> fun : funs) { @@ -3814,29 +3851,33 @@ public class CompletableFutureTest exten AtomicReference firstFailure = new AtomicReference<>(null); } - // Monadic "plus" + /** Implements "monadic plus". */ static CompletableFuture plus(CompletableFuture f, CompletableFuture g) { PlusFuture plus = new PlusFuture(); BiConsumer action = (T result, Throwable ex) -> { - if (ex == null) { - if (plus.complete(result)) - if (plus.firstFailure.get() != null) + try { + if (ex == null) { + if (plus.complete(result)) + if (plus.firstFailure.get() != null) + plus.firstFailure.set(null); + } + else if (plus.firstFailure.compareAndSet(null, ex)) { + if (plus.isDone()) plus.firstFailure.set(null); - } - else if (plus.firstFailure.compareAndSet(null, ex)) { - if (plus.isDone()) - plus.firstFailure.set(null); - } - else { - // first failure has precedence - Throwable first = plus.firstFailure.getAndSet(null); - - // may fail with "Self-suppression not permitted" - try { first.addSuppressed(ex); } - catch (Exception ignored) {} - - plus.completeExceptionally(first); + } + else { + // first failure has precedence + Throwable first = plus.firstFailure.getAndSet(null); + + // may fail with "Self-suppression not permitted" + try { first.addSuppressed(ex); } + catch (Exception ignored) {} + + plus.completeExceptionally(first); + } + } catch (Throwable unexpected) { + plus.completeExceptionally(unexpected); } }; f.whenComplete(action); @@ -3905,6 +3946,38 @@ public class CompletableFutureTest exten Monad.plus(godot, Monad.unit(5L))); } + /** + * A single CompletableFuture with many dependents. + * A demo of scalability - runtime is O(n). + */ + public void testManyDependents() throws Throwable { + final int n = 1_000; + final CompletableFuture head = new CompletableFuture<>(); + final CompletableFuture complete = CompletableFuture.completedFuture((Void)null); + final AtomicInteger count = new AtomicInteger(0); + for (int i = 0; i < n; i++) { + head.thenRun(() -> count.getAndIncrement()); + head.thenAccept((x) -> count.getAndIncrement()); + head.thenApply((x) -> count.getAndIncrement()); + + head.runAfterBoth(complete, () -> count.getAndIncrement()); + head.thenAcceptBoth(complete, (x, y) -> count.getAndIncrement()); + head.thenCombine(complete, (x, y) -> count.getAndIncrement()); + complete.runAfterBoth(head, () -> count.getAndIncrement()); + complete.thenAcceptBoth(head, (x, y) -> count.getAndIncrement()); + complete.thenCombine(head, (x, y) -> count.getAndIncrement()); + + head.runAfterEither(new CompletableFuture(), () -> count.getAndIncrement()); + head.acceptEither(new CompletableFuture(), (x) -> count.getAndIncrement()); + head.applyToEither(new CompletableFuture(), (x) -> count.getAndIncrement()); + new CompletableFuture().runAfterEither(head, () -> count.getAndIncrement()); + new CompletableFuture().acceptEither(head, (x) -> count.getAndIncrement()); + new CompletableFuture().applyToEither(head, (x) -> count.getAndIncrement()); + } + head.complete(null); + assertEquals(5 * 3 * n, count.get()); + } + // static U join(CompletionStage stage) { // CompletableFuture f = new CompletableFuture<>(); // stage.whenComplete((v, ex) -> {