HTTPCLIENT-2080: add getRetryInterval to HttpRequestRetryStrategy for use on retriable IOExceptions (#356)

This commit is contained in:
Anthony Baldocchi 2022-03-30 09:31:07 -05:00 committed by GitHub
parent c395aad5ad
commit 94017237b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 65 additions and 1 deletions

View File

@ -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.
*

View File

@ -159,7 +159,8 @@ public final class AsyncHttpRequestRetryExec implements AsyncExecChainHandler {
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;
}

View File

@ -113,6 +113,18 @@ public class HttpRequestRetryExec implements ExecChainHandler {
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 {

View File

@ -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 class TestHttpRequestRetryExec {
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);