Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit 054d2ca

Browse files
authored
Merge pull request #66 from pwhittlesea/master
Fix #65: Ensure beforeNextTry is only called before a retry
2 parents 3199fa8 + d1a3ce9 commit 054d2ca

File tree

3 files changed

+71
-10
lines changed

3 files changed

+71
-10
lines changed

src/main/java/com/evanlennick/retry4j/CallExecutor.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,17 @@ public Status<T> execute(Callable<T> callable, String callName) {
7272

7373
try {
7474
for (tries = 0; tries < maxTries && !attemptStatus.wasSuccessful(); tries++) {
75+
if (tries > 0) {
76+
handleBeforeNextTry(millisBetweenTries, tries);
77+
logger.trace("Retry4j retrying for time number {}", tries);
78+
}
79+
7580
logger.trace("Retry4j executing callable {}", callable);
7681
attemptStatus = tryCall(callable);
7782

7883
if (!attemptStatus.wasSuccessful()) {
79-
handleRetry(millisBetweenTries, tries + 1);
84+
handleFailedTry(tries + 1);
8085
}
81-
82-
logger.trace("Retry4j retrying for time number {}", tries);
8386
}
8487

8588
refreshRetryStatus(attemptStatus.wasSuccessful(), tries);
@@ -142,18 +145,19 @@ private AttemptStatus<T> tryCall(Callable<T> callable) throws UnexpectedExceptio
142145
return attemptStatus;
143146
}
144147

145-
private void handleRetry(long millisBetweenTries, int tries) {
148+
private void handleBeforeNextTry(final long millisBetweenTries, final int tries) {
149+
sleep(millisBetweenTries, tries);
150+
if (null != beforeNextTryListener) {
151+
beforeNextTryListener.onEvent(status);
152+
}
153+
}
154+
155+
private void handleFailedTry(int tries) {
146156
refreshRetryStatus(false, tries);
147157

148158
if (null != afterFailedTryListener) {
149159
afterFailedTryListener.onEvent(status);
150160
}
151-
152-
sleep(millisBetweenTries, tries);
153-
154-
if (null != beforeNextTryListener) {
155-
beforeNextTryListener.onEvent(status);
156-
}
157161
}
158162

159163
private void refreshRetryStatus(boolean success, int tries) {

src/test/java/com/evanlennick/retry4j/CallExecutorTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,40 @@
11
package com.evanlennick.retry4j;
22

3+
import com.evanlennick.retry4j.backoff.BackoffStrategy;
34
import com.evanlennick.retry4j.config.RetryConfig;
45
import com.evanlennick.retry4j.config.RetryConfigBuilder;
56
import com.evanlennick.retry4j.exception.RetriesExhaustedException;
67
import com.evanlennick.retry4j.exception.UnexpectedException;
8+
9+
import org.mockito.Mock;
10+
import org.mockito.MockitoAnnotations;
711
import org.testng.annotations.BeforeMethod;
812
import org.testng.annotations.Test;
913

1014
import java.io.FileNotFoundException;
1115
import java.io.IOException;
16+
import java.time.Duration;
1217
import java.time.temporal.ChronoUnit;
1318
import java.util.Random;
1419
import java.util.concurrent.Callable;
1520

1621
import static org.assertj.core.api.Assertions.assertThat;
1722
import static org.assertj.core.api.Assertions.fail;
1823
import static org.assertj.core.api.Assertions.within;
24+
import static org.mockito.Mockito.verify;
25+
import static org.mockito.Mockito.when;
1926

2027
public class CallExecutorTest {
2128

29+
@Mock
30+
private BackoffStrategy mockBackOffStrategy;
31+
2232
private RetryConfigBuilder retryConfigBuilder;
2333

2434
@BeforeMethod
2535
public void setup() {
36+
MockitoAnnotations.initMocks(this);
37+
2638
boolean configValidationEnabled = false;
2739
this.retryConfigBuilder = new RetryConfigBuilder(configValidationEnabled);
2840
}
@@ -221,4 +233,29 @@ public void verifyRetryingIndefinitely() throws Exception {
221233
fail("Retries should never be exhausted!");
222234
}
223235
}
236+
237+
@Test
238+
public void verifyRetryPolicyTimeoutIsUsed() {
239+
Callable<Object> callable = () -> {
240+
throw new RuntimeException();
241+
};
242+
243+
Duration delayBetweenTriesDuration = Duration.ofSeconds(17);
244+
when(mockBackOffStrategy.getDurationToWait(1, delayBetweenTriesDuration)).thenReturn(Duration.ofSeconds(5));
245+
246+
RetryConfig retryConfig = retryConfigBuilder
247+
.withMaxNumberOfTries(2)
248+
.retryOnAnyException()
249+
.withDelayBetweenTries(delayBetweenTriesDuration)
250+
.withBackoffStrategy(mockBackOffStrategy)
251+
.build();
252+
253+
final long before = System.currentTimeMillis();
254+
try {
255+
new CallExecutor<>(retryConfig).execute(callable);
256+
} catch (RetriesExhaustedException ignored) {}
257+
258+
assertThat(System.currentTimeMillis() - before).isGreaterThan(5000);
259+
verify(mockBackOffStrategy).getDurationToWait(1, delayBetweenTriesDuration);
260+
}
224261
}

src/test/java/com/evanlennick/retry4j/CallExecutorTest_ListenersTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.time.temporal.ChronoUnit;
1111
import java.util.concurrent.Callable;
1212

13+
import static org.mockito.Mockito.after;
1314
import static org.mockito.Mockito.isA;
1415
import static org.mockito.Mockito.timeout;
1516
import static org.mockito.Mockito.verify;
@@ -78,6 +79,25 @@ public void verifyBeforeNextTryListener() {
7879
verify(dummyMock, timeout(1000).times(2)).listenersCallThis();
7980
}
8081

82+
@Test
83+
public void verifyBeforeNextTryListener_NotCalledAfterLastFailure() {
84+
when(dummyMock.callableCallThis())
85+
.thenThrow(new NullPointerException())
86+
.thenThrow(new NullPointerException())
87+
.thenThrow(new NullPointerException())
88+
.thenThrow(new NullPointerException())
89+
.thenThrow(new IllegalArgumentException());
90+
91+
executor.beforeNextTry(status -> dummyMock.listenersCallThis(status.getLastExceptionThatCausedRetry()));
92+
try {
93+
executor.execute(callable);
94+
} catch (Exception e) {}
95+
96+
// the beforeNextTry listener should only be called before a retry (5 attempts == 4 retries)
97+
verify(dummyMock, timeout(1000).times(4)).listenersCallThis(isA(NullPointerException.class));
98+
verify(dummyMock, after(1000).never()).listenersCallThis(isA(IllegalArgumentException.class));
99+
}
100+
81101
@Test
82102
public void verifyOnSuccessListener() {
83103
when(dummyMock.callableCallThis())

0 commit comments

Comments
 (0)