diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/HttpRequestRetryStrategy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/HttpRequestRetryStrategy.java index 56012eec2..3fcd9912c 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/HttpRequestRetryStrategy.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/HttpRequestRetryStrategy.java @@ -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. * diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncHttpRequestRetryExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncHttpRequestRetryExec.java index 3101ade5e..a38bfd42f 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncHttpRequestRetryExec.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncHttpRequestRetryExec.java @@ -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; } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpRequestRetryExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpRequestRetryExec.java index 71e222135..75e75d19f 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpRequestRetryExec.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpRequestRetryExec.java @@ -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 { diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestHttpRequestRetryExec.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestHttpRequestRetryExec.java index 34482bf38..b885ae7ba 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestHttpRequestRetryExec.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestHttpRequestRetryExec.java @@ -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);