Skip to content

Commit 9c95dea

Browse files
committed
MLE-23230 Better name for retry interceptor
And added comments to explain what the existing app-level retry support is doing vs what this for-now-undocumented interceptor will be doing.
1 parent 12fb4c1 commit 9c95dea

File tree

5 files changed

+48
-25
lines changed

5 files changed

+48
-25
lines changed

marklogic-client-api-functionaltests/src/test/java/com/marklogic/client/functionaltest/ConnectedRESTQA.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import com.marklogic.client.admin.ServerConfigurationManager;
1818
import com.marklogic.client.extra.okhttpclient.OkHttpClientConfigurator;
1919
import com.marklogic.client.impl.SSLUtil;
20-
import com.marklogic.client.impl.okhttp.RetryInterceptor;
20+
import com.marklogic.client.impl.okhttp.RetryIOExceptionInterceptor;
2121
import com.marklogic.client.io.DocumentMetadataHandle;
2222
import com.marklogic.client.io.DocumentMetadataHandle.Capability;
2323
import com.marklogic.client.query.QueryManager;
@@ -50,7 +50,7 @@ public abstract class ConnectedRESTQA {
5050
static {
5151
DatabaseClientFactory.removeConfigurators();
5252
DatabaseClientFactory.addConfigurator((OkHttpClientConfigurator) client ->
53-
client.addInterceptor(new RetryInterceptor(3, 1000, 2, 8000)));
53+
client.addInterceptor(new RetryIOExceptionInterceptor(3, 1000, 2, 8000)));
5454
}
5555

5656
private static Properties testProperties = null;

marklogic-client-api/src/main/java/com/marklogic/client/impl/OkHttpServices.java

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,19 @@ public class OkHttpServices implements RESTServices {
9999

100100
private boolean released = false;
101101

102+
/**
103+
* The next 4 fields implement an application-level retry that only works for certain HTTP status codes. It will not
104+
* attempt a retry on any IOException or any type of connection failure. Sadly, the logic that uses these fields is
105+
* in several places and is slightly different in each place. It's also not possible to implement this logic in an
106+
* OkHttp interceptor as the logic needs access to details that are not available to an interceptor.
107+
*/
102108
private final Random randRetry = new Random();
103-
104109
private int maxDelay = DEFAULT_MAX_DELAY;
105110
private int minRetry = DEFAULT_MIN_RETRY;
111+
private final Set<Integer> retryStatus = new HashSet<>();
106112

107113
private boolean checkFirstRequest = true;
108114

109-
private final Set<Integer> retryStatus = new HashSet<>();
110-
111115
static protected class ThreadState {
112116
boolean isFirstRequest;
113117

@@ -510,9 +514,6 @@ private Response sendRequestWithRetry(
510514
long startTime = System.currentTimeMillis();
511515
int nextDelay = 0;
512516
int retry = 0;
513-
/*
514-
* This loop is for retrying the request if the service is unavailable
515-
*/
516517
for (; retry < minRetry || (System.currentTimeMillis() - startTime) < maxDelay; retry++) {
517518
if (nextDelay > 0) {
518519
try {
@@ -542,10 +543,10 @@ private Response sendRequestWithRetry(
542543
break;
543544
}
544545
/*
545-
* This code will be executed whenever the service is unavailable.
546-
* When the service becomes unavailable, we close the Response
547-
* we got and retry it to try and get a new Response
548-
*/
546+
* This code will be executed whenever the service is unavailable.
547+
* When the service becomes unavailable, we close the Response
548+
* we got and retry it to try and get a new Response
549+
*/
549550
closeResponse(response);
550551
/*
551552
* There are scenarios where we don't want to retry and we just want to
@@ -572,9 +573,9 @@ private Response sendRequestWithRetry(
572573
" seconds after " + retry + " retries");
573574
}
574575
/*
575-
* Once we break from the retry loop, we just return the Response
576-
* back to the caller in order to proceed with the flow
577-
*/
576+
* Once we break from the retry loop, we just return the Response
577+
* back to the caller in order to proceed with the flow
578+
*/
578579
return response;
579580
}
580581

@@ -1199,6 +1200,7 @@ private TemporalDescriptor putPostDocumentImpl(RequestLogger reqlog, String meth
11991200
}
12001201
}
12011202

1203+
// This would be the "doFunction"
12021204
Object value = handleBase.sendContent();
12031205
if (value == null) {
12041206
throw new IllegalArgumentException(
@@ -5442,6 +5444,12 @@ public void writeTo(BufferedSink sink) throws IOException {
54425444
throw new IllegalStateException("Cannot write object of type: " + obj.getClass());
54435445
}
54445446
}
5447+
5448+
@Override
5449+
public boolean isOneShot() {
5450+
// Added in 8.0.0 to work with the retry interceptor so it knows whether the body can be retried or not.
5451+
return obj instanceof InputStream;
5452+
}
54455453
}
54465454

54475455
// API First Changes

marklogic-client-api/src/main/java/com/marklogic/client/impl/StreamingOutputImpl.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,10 @@ public void writeTo(BufferedSink sink) throws IOException {
4545

4646
handle.write(out);
4747
}
48+
49+
@Override
50+
public boolean isOneShot() {
51+
// Added in 8.0.0; streaming output cannot be retried as the stream is consumed on first write.
52+
return true;
53+
}
4854
}

marklogic-client-api/src/main/java/com/marklogic/client/impl/okhttp/RetryInterceptor.java renamed to marklogic-client-api/src/main/java/com/marklogic/client/impl/okhttp/RetryIOExceptionInterceptor.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,22 @@
1414
import java.net.UnknownHostException;
1515

1616
/**
17-
* OkHttp interceptor that retries requests on certain connection failures,
18-
* which can be helpful when MarkLogic is temporarily unavailable during restarts.
17+
* Experimental interceptor added in 8.0.0 for retrying requests that fail due to connection issues. These issues are
18+
* not handled by the application-level retry support in OkHttpServices, which only handles retries based on certain
19+
* HTTP status codes. The main limitation of this approach is that it cannot retry a request that has a one-shot body,
20+
* such as a streaming body. But for requests that don't have one-shot bodies, this interceptor can be helpful for
21+
* retrying requests that fail due to temporary network issues or MarkLogic restarts.
1922
*/
20-
public class RetryInterceptor implements Interceptor {
23+
public class RetryIOExceptionInterceptor implements Interceptor {
2124

22-
private final static Logger logger = org.slf4j.LoggerFactory.getLogger(RetryInterceptor.class);
25+
private final static Logger logger = org.slf4j.LoggerFactory.getLogger(RetryIOExceptionInterceptor.class);
2326

2427
private final int maxRetries;
2528
private final long initialDelayMs;
2629
private final double backoffMultiplier;
2730
private final long maxDelayMs;
2831

29-
public RetryInterceptor(int maxRetries, long initialDelayMs, double backoffMultiplier, long maxDelayMs) {
32+
public RetryIOExceptionInterceptor(int maxRetries, long initialDelayMs, double backoffMultiplier, long maxDelayMs) {
3033
this.maxRetries = maxRetries;
3134
this.initialDelayMs = initialDelayMs;
3235
this.backoffMultiplier = backoffMultiplier;
@@ -37,11 +40,18 @@ public RetryInterceptor(int maxRetries, long initialDelayMs, double backoffMulti
3740
public Response intercept(Chain chain) throws IOException {
3841
Request request = chain.request();
3942

43+
// isOneShot is overridden by some of our subclasses of RequestBody to return true when the body is some
44+
// kind of stream.
45+
final boolean requestCannotBeRetried = request.body() != null && request.body().isOneShot();
46+
if (requestCannotBeRetried) {
47+
return chain.proceed(request);
48+
}
49+
4050
for (int attempt = 0; attempt <= maxRetries; attempt++) {
4151
try {
4252
return chain.proceed(request);
4353
} catch (IOException e) {
44-
if (attempt == maxRetries || !isRetryableException(e)) {
54+
if (attempt == maxRetries || !isRetryableIOException(e)) {
4555
logger.warn("Not retryable: {}; {}", e.getClass(), e.getMessage());
4656
throw e;
4757
}
@@ -58,7 +68,7 @@ public Response intercept(Chain chain) throws IOException {
5868
throw new IllegalStateException("Unexpected end of retry loop");
5969
}
6070

61-
private boolean isRetryableException(IOException e) {
71+
private boolean isRetryableIOException(IOException e) {
6272
return e instanceof ConnectException ||
6373
e instanceof SocketTimeoutException ||
6474
e instanceof UnknownHostException ||

marklogic-client-api/src/test/java/com/marklogic/client/test/Common.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@
99
import com.marklogic.client.DatabaseClientBuilder;
1010
import com.marklogic.client.DatabaseClientFactory;
1111
import com.marklogic.client.extra.okhttpclient.OkHttpClientConfigurator;
12-
import com.marklogic.client.impl.okhttp.RetryInterceptor;
12+
import com.marklogic.client.impl.okhttp.RetryIOExceptionInterceptor;
1313
import com.marklogic.client.io.DocumentMetadataHandle;
1414
import com.marklogic.mgmt.ManageClient;
1515
import com.marklogic.mgmt.ManageConfig;
16-
import okhttp3.OkHttpClient;
1716
import org.springframework.util.FileCopyUtils;
1817
import org.w3c.dom.DOMException;
1918
import org.w3c.dom.Document;
@@ -35,7 +34,7 @@ public class Common {
3534
static {
3635
DatabaseClientFactory.removeConfigurators();
3736
DatabaseClientFactory.addConfigurator((OkHttpClientConfigurator) client ->
38-
client.addInterceptor(new RetryInterceptor(3, 1000, 2, 8000)));
37+
client.addInterceptor(new RetryIOExceptionInterceptor(3, 1000, 2, 8000)));
3938
}
4039

4140
final public static String USER = "rest-writer";

0 commit comments

Comments
 (0)