Skip to content
Permalink
Browse files
HTTPCLIENT-2080: add getRetryInterval to HttpRequestRetryStrategy for…
… use on retriable IOExceptions (#356)
  • Loading branch information
ajbaldocchi committed Mar 30, 2022
1 parent c395aad commit 94017237b2046574267f46bf6f441bc560378b58
Showing 4 changed files with 65 additions and 1 deletion.
@@ -75,6 +75,22 @@ public interface HttpRequestRetryStrategy {
*/
boolean retryRequest(HttpResponse response, int execCount, HttpContext context);


/**
* Determines the retry interval between subsequent retries.
*
* @param request the request failed due to an I/O exception
* @param exception the exception that occurred
* @param execCount the number of times this method has been
* unsuccessfully executed
* @param context the context for the request execution
*
* @return the retry interval between subsequent retries
*/
default TimeValue getRetryInterval(HttpRequest request, IOException exception, int execCount, HttpContext context) {
return TimeValue.ZERO_MILLISECONDS;
}

/**
* Determines the retry interval between subsequent retries.
*
@@ -159,7 +159,8 @@ public void failed(final Exception cause) {
entityProducer.releaseResources();
}
state.retrying = true;
scope.execCount.incrementAndGet();
final int execCount = scope.execCount.incrementAndGet();
state.delay = retryStrategy.getRetryInterval(request, (IOException) cause, execCount - 1, clientContext);
scope.scheduler.scheduleExecution(request, entityProducer, scope, asyncExecCallback, state.delay);
return;
}
@@ -113,6 +113,18 @@ public ClassicHttpResponse execute(
LOG.info("Recoverable I/O exception ({}) caught when processing request to {}",
ex.getClass().getName(), route);
}
final TimeValue nextInterval = retryStrategy.getRetryInterval(request, ex, execCount, context);
if (TimeValue.isPositive(nextInterval)) {
try {
if (LOG.isDebugEnabled()) {
LOG.debug("{} wait for {}", exchangeId, nextInterval);
}
nextInterval.sleep();
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
throw new InterruptedIOException();
}
}
currentRequest = ClassicRequestBuilder.copy(scope.originalRequest).build();
continue;
} else {
@@ -62,6 +62,8 @@ public class TestHttpRequestRetryExec {
private ExecChain chain;
@Mock
private ExecRuntime endpoint;
@Mock
private TimeValue nextInterval;

private HttpRequestRetryExec retryExec;
private HttpHost target;
@@ -103,6 +105,39 @@ public void testFundamentals1() throws Exception {
Mockito.verify(response, Mockito.times(1)).close();
}

@Test
public void testRetrySleepOnIOException() throws Exception {
final HttpRoute route = new HttpRoute(target);
final HttpGet request = new HttpGet("/test");
final HttpClientContext context = HttpClientContext.create();

final ClassicHttpResponse response = Mockito.mock(ClassicHttpResponse.class);

Mockito.when(chain.proceed(
Mockito.same(request),
Mockito.any())).thenThrow(new IOException("Ka-boom"));
Mockito.when(retryStrategy.retryRequest(
Mockito.any(),
Mockito.any(),
Mockito.anyInt(),
Mockito.any())).thenReturn(Boolean.TRUE, Boolean.FALSE);
Mockito.when(retryStrategy.getRetryInterval(
Mockito.any(),
Mockito.any(),
Mockito.anyInt(),
Mockito.any())).thenReturn(nextInterval);
Mockito.when(nextInterval.getDuration()).thenReturn(100L);
Mockito.when(nextInterval.compareTo(Mockito.any())).thenReturn(-1);

final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, endpoint, context);
retryExec.execute(request, scope, chain);

Mockito.verify(chain, Mockito.times(2)).proceed(
Mockito.any(),
Mockito.same(scope));
Mockito.verify(nextInterval, Mockito.times(1)).sleep();
}

@Test
public void testRetryIntervalGreaterResponseTimeout() throws Exception {
final HttpRoute route = new HttpRoute(target);

0 comments on commit 9401723

Please sign in to comment.