diff --git a/CHANGELOG.md b/CHANGELOG.md index 1010be5..f5eb631 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1 @@ -See [Releases](https://github.com/rnorth/duct-tape/releases) +See [Releases](https://github.com/rnorth/duct-tape/releases) \ No newline at end of file diff --git a/src/main/java/org/rnorth/ducttape/unreliables/Unreliables.java b/src/main/java/org/rnorth/ducttape/unreliables/Unreliables.java index b1ba669..03fb85a 100644 --- a/src/main/java/org/rnorth/ducttape/unreliables/Unreliables.java +++ b/src/main/java/org/rnorth/ducttape/unreliables/Unreliables.java @@ -7,6 +7,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import static org.rnorth.ducttape.Preconditions.check; @@ -34,9 +35,10 @@ public static T retryUntilSuccess(final int timeout, @NotNull final TimeUnit final int[] attempt = {0}; final Exception[] lastException = {null}; + final AtomicBoolean doContinue = new AtomicBoolean(true); try { return Timeouts.getWithTimeout(timeout, timeUnit, () -> { - while (true) { + while (doContinue.get()) { try { return lambda.call(); } catch (Exception e) { @@ -45,6 +47,7 @@ public static T retryUntilSuccess(final int timeout, @NotNull final TimeUnit lastException[0] = e; } } + return null; }); } catch (org.rnorth.ducttape.TimeoutException e) { if (lastException[0] != null) { @@ -52,6 +55,8 @@ public static T retryUntilSuccess(final int timeout, @NotNull final TimeUnit } else { throw new org.rnorth.ducttape.TimeoutException(e); } + } finally { + doContinue.set(false); } } diff --git a/src/test/java/org/rnorth/ducttape/unreliables/UnreliablesTest.java b/src/test/java/org/rnorth/ducttape/unreliables/UnreliablesTest.java index 8d60fe3..dd6f434 100644 --- a/src/test/java/org/rnorth/ducttape/unreliables/UnreliablesTest.java +++ b/src/test/java/org/rnorth/ducttape/unreliables/UnreliablesTest.java @@ -5,8 +5,10 @@ import org.rnorth.ducttape.TimeoutException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; +import static org.rnorth.visibleassertions.VisibleAssertions.assertFalse; import static org.rnorth.visibleassertions.VisibleAssertions.assertThrows; import static org.rnorth.visibleassertions.VisibleAssertions.fail; @@ -115,6 +117,24 @@ public void testRetryUntilSuccessFailsWhenOutsideTimeoutWindow() throws Exceptio } } + @Test + public void testRetryUntilSuccessLambdaStopsBeingCalledAfterTimeout() throws Exception { + AtomicBoolean lambdaCalled = new AtomicBoolean(); + try { + Unreliables.retryUntilSuccess(200, TimeUnit.MILLISECONDS, () -> { + lambdaCalled.set(true); + throw new RuntimeException(); + }); + fail("Expected TimeoutException on retryUntilSuccess that always fails."); + } catch (TimeoutException e) { + // ok + Thread.sleep(200); // give worker thread time to stop calling the lambda + lambdaCalled.set(false); + Thread.sleep(200); // give worker thread time to call the lambda if it is still running + assertFalse("Lambda should stop being executed when retryUntilSuccess times out.", lambdaCalled.get()); + } + } + @Test public void testRetryUntilSuccessFailsWhenOutsideTimeoutWindowAndCapturesException() throws Exception { try {