diff --git a/core/src/main/java/feign/AsynchronousMethodHandler.java b/core/src/main/java/feign/AsynchronousMethodHandler.java index 2376d00a2..82466fc15 100644 --- a/core/src/main/java/feign/AsynchronousMethodHandler.java +++ b/core/src/main/java/feign/AsynchronousMethodHandler.java @@ -88,7 +88,7 @@ public Object invoke(Object[] argv) throws Throwable { private CompletableFuture executeAndDecode(RequestTemplate template, Options options, Retryer retryer) { - CompletableFuture resultFuture = new CompletableFuture<>(); + CancellableFuture resultFuture = new CancellableFuture<>(); executeAndDecode(template, options) .whenComplete((response, throwable) -> { @@ -98,8 +98,8 @@ private CompletableFuture executeAndDecode(RequestTemplate template, logger.logRetry(metadata.configKey(), logLevel); } - executeAndDecode(template, options, retryer) - .whenComplete(pipeTo(resultFuture)); + resultFuture.setInner( + executeAndDecode(template, options, retryer)); } } else { resultFuture.complete(response); @@ -109,6 +109,38 @@ private CompletableFuture executeAndDecode(RequestTemplate template, return resultFuture; } + private static class CancellableFuture extends CompletableFuture { + private CompletableFuture inner = null; + + public void setInner(CompletableFuture value) { + inner = value; + inner.whenComplete(pipeTo(this)); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + final boolean result = super.cancel(mayInterruptIfRunning); + if (inner != null) { + inner.cancel(mayInterruptIfRunning); + } + return result; + } + + private static BiConsumer pipeTo(CompletableFuture completableFuture) { + return (value, throwable) -> { + if (completableFuture.isDone()) { + return; + } + + if (throwable != null) { + completableFuture.completeExceptionally(throwable); + } else { + completableFuture.complete(value); + } + }; + } + } + private boolean shouldRetry(Retryer retryer, Throwable throwable, CompletableFuture resultFuture) { @@ -136,16 +168,6 @@ private boolean shouldRetry(Retryer retryer, } } - private static BiConsumer pipeTo(CompletableFuture completableFuture) { - return (value, throwable) -> { - if (throwable != null) { - completableFuture.completeExceptionally(throwable); - } else { - completableFuture.complete(value); - } - }; - } - private CompletableFuture executeAndDecode(RequestTemplate template, Options options) { Request request = targetRequest(template); diff --git a/core/src/test/java/feign/AsyncFeignTest.java b/core/src/test/java/feign/AsyncFeignTest.java index b06b80eb1..bd29ed906 100644 --- a/core/src/test/java/feign/AsyncFeignTest.java +++ b/core/src/test/java/feign/AsyncFeignTest.java @@ -662,7 +662,7 @@ public void whenReturnTypeIsResponseNoErrorHandling() throws Throwable { } @ParameterizedTest - @ValueSource(ints = {1}) // TODO: Modify it to work even if it is more than 2 tries + @ValueSource(ints = {1, 5, 10, 100, 1000}) public void cancelRetry(final int expectedTryCount) throws Throwable { // Arrange final CompletableFuture maximumTryCompleted = new CompletableFuture<>();