--- jsr166/src/test/tck/CompletableFutureTest.java 2015/11/15 00:37:50 1.128 +++ jsr166/src/test/tck/CompletableFutureTest.java 2015/11/15 23:31:51 1.138 @@ -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,8 @@ public class CompletableFutureTest exten checkCompletedWithWrappedException(g, ex1); checkCompletedExceptionally(f, ex1); + assertEquals(1, ex1.getSuppressed().length); + assertSame(ex2, ex1.getSuppressed()[0]); assertEquals(1, a.get()); }} @@ -1052,9 +1059,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 +1088,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 +1117,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 +1132,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()); }} @@ -3644,7 +3658,7 @@ public class CompletableFutureTest exten funs.add((y) -> m.thenAcceptBoth(y, v42, new SubtractAction(m))); funs.add((y) -> m.thenCombine(y, v42, 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))); @@ -3764,16 +3778,16 @@ public class CompletableFutureTest exten } static class Monad { - static class MonadError extends Error { - public MonadError() { super("monadic zero"); } + static class ZeroException extends RuntimeException { + public ZeroException() { super("monadic zero"); } } // "return", "unit" static CompletableFuture unit(T value) { return completedFuture(value); } // monadic zero ? - static CompletableFuture zero(T value) { - return failedFuture(new MonadError()); + static CompletableFuture zero() { + return failedFuture(new ZeroException()); } // >=> static Function> compose @@ -3787,7 +3801,7 @@ public class CompletableFutureTest exten f.getNow(null); throw new AssertionFailedError("should throw"); } catch (CompletionException success) { - assertTrue(success.getCause() instanceof MonadError); + assertTrue(success.getCause() instanceof ZeroException); } } @@ -3814,22 +3828,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 fail) -> { - if (result != null) { - if (plus.complete(result)) - if (plus.firstFailure.get() != null) + BiConsumer action = (T result, Throwable ex) -> { + 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, fail)) { - if (plus.isDone()) - plus.firstFailure.set(null); - } else { - if (plus.completeExceptionally(fail)) - 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); + } + } catch (Throwable unexpected) { + plus.completeExceptionally(unexpected); } }; f.whenComplete(action); @@ -3843,59 +3868,59 @@ public class CompletableFutureTest exten * https://en.wikipedia.org/wiki/Monad_(functional_programming)#Additive_monads */ public void testAdditiveMonad() throws Throwable { + Function> unit = Monad::unit; + CompletableFuture zero = Monad.zero(); + // Some mutually non-commutative functions Function> triple - = (x) -> completedFuture(3 * x); + = (x) -> Monad.unit(3 * x); Function> inc - = (x) -> completedFuture(x + 1); - - Function> unit = Monad::unit; - Function> zero = Monad::zero; + = (x) -> Monad.unit(x + 1); // unit is a right identity: m >>= unit === m - Monad.assertFutureEquals( - inc.apply(5L).thenCompose(unit), - inc.apply(5L)); + Monad.assertFutureEquals(inc.apply(5L).thenCompose(unit), + inc.apply(5L)); // unit is a left identity: (unit x) >>= f === f x - Monad.assertFutureEquals( - unit.apply(5L).thenCompose(inc), - inc.apply(5L)); + Monad.assertFutureEquals(unit.apply(5L).thenCompose(inc), + inc.apply(5L)); + // associativity: (m >>= f) >>= g === m >>= ( \x -> (f x >>= g) ) Monad.assertFutureEquals( unit.apply(5L).thenCompose(inc).thenCompose(triple), unit.apply(5L).thenCompose((x) -> inc.apply(x).thenCompose(triple))); + // The case for CompletableFuture as an additive monad is weaker... + // zero is a monadic zero - Monad.assertZero(zero.apply(5L)); - // left zero: zero >>= f === zero - Monad.assertZero(zero.apply(5L).thenCompose(inc)); - // right zero: f >>= zero === zero - Monad.assertZero(inc.apply(5L).thenCompose(zero)); + Monad.assertZero(zero); - // inc plus zero === inc - Monad.assertFutureEquals( - inc.apply(5L), - Monad.plus(inc.apply(5L), zero.apply(5L))); - // zero plus inc === inc - Monad.assertFutureEquals( - inc.apply(5L), - Monad.plus(zero.apply(5L), inc.apply(5L))); + // left zero: zero >>= f === zero + Monad.assertZero(zero.thenCompose(inc)); + // right zero: f >>= (\x -> zero) === zero + Monad.assertZero(inc.apply(5L).thenCompose((x) -> zero)); + + // f plus zero === f + Monad.assertFutureEquals(Monad.unit(5L), + Monad.plus(Monad.unit(5L), zero)); + // zero plus f === f + Monad.assertFutureEquals(Monad.unit(5L), + Monad.plus(zero, Monad.unit(5L))); // zero plus zero === zero - Monad.assertZero(Monad.plus(zero.apply(5L), zero.apply(5L))); + Monad.assertZero(Monad.plus(zero, zero)); { - CompletableFuture f = Monad.plus(inc.apply(5L), inc.apply(8L)); - assertTrue(f.get() == 6L || f.get() == 9L); + CompletableFuture f = Monad.plus(Monad.unit(5L), + Monad.unit(8L)); + // non-determinism + assertTrue(f.get() == 5L || f.get() == 8L); } CompletableFuture godot = new CompletableFuture<>(); - // inc plus godot === inc (doesn't wait for godot) - Monad.assertFutureEquals( - inc.apply(5L), - Monad.plus(inc.apply(5L), godot)); - // godot plus inc === inc (doesn't wait for godot) - Monad.assertFutureEquals( - inc.apply(5L), - Monad.plus(godot, inc.apply(5L))); + // f plus godot === f (doesn't wait for godot) + Monad.assertFutureEquals(Monad.unit(5L), + Monad.plus(Monad.unit(5L), godot)); + // godot plus f === f (doesn't wait for godot) + Monad.assertFutureEquals(Monad.unit(5L), + Monad.plus(godot, Monad.unit(5L))); } // static U join(CompletionStage stage) {