From 0dbaf3090d53b927381bb37974bb3cba5ab9178e Mon Sep 17 00:00:00 2001 From: Donghyeon Kim Date: Sat, 22 Oct 2022 18:16:08 +0900 Subject: [PATCH] Support cancel feature for AsyncFeign --- .../java/feign/AsynchronousMethodHandler.java | 2 +- core/src/test/java/feign/AsyncFeignTest.java | 49 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/feign/AsynchronousMethodHandler.java b/core/src/main/java/feign/AsynchronousMethodHandler.java index 1c91a16b4..2376d00a2 100644 --- a/core/src/main/java/feign/AsynchronousMethodHandler.java +++ b/core/src/main/java/feign/AsynchronousMethodHandler.java @@ -93,7 +93,7 @@ private CompletableFuture executeAndDecode(RequestTemplate template, executeAndDecode(template, options) .whenComplete((response, throwable) -> { if (throwable != null) { - if (shouldRetry(retryer, throwable, resultFuture)) { + if (!resultFuture.isDone() && shouldRetry(retryer, throwable, resultFuture)) { if (logLevel != Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); } diff --git a/core/src/test/java/feign/AsyncFeignTest.java b/core/src/test/java/feign/AsyncFeignTest.java index 6ccfc714d..10b00fd4a 100644 --- a/core/src/test/java/feign/AsyncFeignTest.java +++ b/core/src/test/java/feign/AsyncFeignTest.java @@ -48,10 +48,13 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; @@ -656,6 +659,52 @@ public void whenReturnTypeIsResponseNoErrorHandling() throws Throwable { execs.shutdown(); } + @Test + public void cancelRetry() throws Throwable { + // Arrange + final int expectedTryCount = 1; // TODO: Modify it to work even if it is more than 2 tries + final CompletableFuture maximumTryCompleted = new CompletableFuture<>(); + final AtomicInteger actualTryCount = new AtomicInteger(); + final AtomicBoolean isCancelled = new AtomicBoolean(true); + + final int RUNNING_TIME_MILLIS = 100; + final ExecutorService execs = Executors.newSingleThreadExecutor(); + final AsyncClient clientMock = (request, options, requestContext) -> + CompletableFuture.supplyAsync(() -> { + final int tryCount = actualTryCount.addAndGet(1); + if (tryCount < expectedTryCount) { + throw new CompletionException(new IOException()); + } + + if (tryCount > expectedTryCount) { + isCancelled.set(false); + throw new CompletionException(new IOException()); + } + + maximumTryCompleted.complete(true); + try { + Thread.sleep(RUNNING_TIME_MILLIS); + throw new IOException(); + } catch (Throwable e) { + throw new CompletionException(e); + } + }, execs); + final TestInterfaceAsync sut = AsyncFeign.builder() + .client(clientMock) + .target(TestInterfaceAsync.class, "http://localhost:" + server.getPort()); + + // Act + final CompletableFuture actual = sut.post(); + maximumTryCompleted.join(); + actual.cancel(true); + Thread.sleep(RUNNING_TIME_MILLIS * 5); + + // Assert + assertThat(actualTryCount.get()).isEqualTo(expectedTryCount); + assertThat(isCancelled.get()).isTrue(); + execs.shutdown(); + } + private static class MockRetryer implements Retryer { boolean tripped;