Skip to content

Commit 1c1d561

Browse files
authored
Expand OkHttp retry exception predicate (#7047)
1 parent 5e1a397 commit 1c1d561

File tree

3 files changed

+42
-30
lines changed

3 files changed

+42
-30
lines changed

exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/RetryInterceptor.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import java.io.IOException;
1212
import java.net.ConnectException;
1313
import java.net.SocketTimeoutException;
14-
import java.util.Locale;
14+
import java.net.UnknownHostException;
1515
import java.util.StringJoiner;
1616
import java.util.concurrent.ThreadLocalRandom;
1717
import java.util.concurrent.TimeUnit;
@@ -154,14 +154,15 @@ boolean shouldRetryOnException(IOException e) {
154154

155155
// Visible for testing
156156
static boolean isRetryableException(IOException e) {
157+
// Known retryable SocketTimeoutException messages: null, "connect timed out", "timeout"
158+
// Known retryable ConnectTimeout messages: "Failed to connect to
159+
// localhost/[0:0:0:0:0:0:0:1]:62611"
160+
// Known retryable UnknownHostException messages: "xxxxxx.com"
157161
if (e instanceof SocketTimeoutException) {
158-
String message = e.getMessage();
159-
// Connect timeouts can produce SocketTimeoutExceptions with no message, or with "connect
160-
// timed out"
161-
return message == null || message.toLowerCase(Locale.ROOT).contains("connect timed out");
162+
return true;
162163
} else if (e instanceof ConnectException) {
163-
// Exceptions resemble: java.net.ConnectException: Failed to connect to
164-
// localhost/[0:0:0:0:0:0:0:1]:62611
164+
return true;
165+
} else if (e instanceof UnknownHostException) {
165166
return true;
166167
}
167168
return false;

exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/RetryInterceptorTest.java

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@
2727
import java.net.HttpRetryException;
2828
import java.net.ServerSocket;
2929
import java.net.SocketTimeoutException;
30+
import java.net.UnknownHostException;
3031
import java.time.Duration;
3132
import java.util.concurrent.TimeUnit;
3233
import java.util.function.Predicate;
3334
import java.util.logging.Level;
3435
import java.util.logging.Logger;
36+
import java.util.stream.Stream;
3537
import okhttp3.OkHttpClient;
3638
import okhttp3.Request;
3739
import okhttp3.Response;
@@ -40,6 +42,8 @@
4042
import org.junit.jupiter.api.extension.ExtendWith;
4143
import org.junit.jupiter.api.extension.RegisterExtension;
4244
import org.junit.jupiter.params.ParameterizedTest;
45+
import org.junit.jupiter.params.provider.Arguments;
46+
import org.junit.jupiter.params.provider.MethodSource;
4347
import org.junit.jupiter.params.provider.ValueSource;
4448
import org.mockito.Mock;
4549
import org.mockito.junit.jupiter.MockitoExtension;
@@ -217,26 +221,30 @@ private OkHttpClient connectTimeoutClient() {
217221
.build();
218222
}
219223

220-
@Test
221-
void isRetryableException() {
222-
// Should retry on connection timeouts, where error message is "Connect timed out" or "connect
223-
// timed out"
224-
assertThat(retrier.shouldRetryOnException(new SocketTimeoutException("Connect timed out")))
225-
.isTrue();
226-
assertThat(retrier.shouldRetryOnException(new SocketTimeoutException("connect timed out")))
227-
.isTrue();
228-
// Shouldn't retry on read timeouts, where error message is "Read timed out"
229-
assertThat(retrier.shouldRetryOnException(new SocketTimeoutException("Read timed out")))
230-
.isFalse();
231-
// Shouldn't retry on write timeouts or other IOException
232-
assertThat(retrier.shouldRetryOnException(new SocketTimeoutException("timeout"))).isFalse();
233-
assertThat(retrier.shouldRetryOnException(new SocketTimeoutException())).isTrue();
234-
assertThat(retrier.shouldRetryOnException(new IOException("error"))).isFalse();
235-
236-
// Testing configured predicate
237-
assertThat(retrier.shouldRetryOnException(new HttpRetryException("error", 400))).isFalse();
238-
assertThat(retrier.shouldRetryOnException(new HttpRetryException("timeout retry", 400)))
239-
.isTrue();
224+
@ParameterizedTest
225+
@MethodSource("isRetryableExceptionArgs")
226+
void isRetryableException(IOException exception, boolean expectedRetryResult) {
227+
assertThat(retrier.shouldRetryOnException(exception)).isEqualTo(expectedRetryResult);
228+
}
229+
230+
private static Stream<Arguments> isRetryableExceptionArgs() {
231+
return Stream.of(
232+
// Should retry on SocketTimeoutExceptions
233+
Arguments.of(new SocketTimeoutException("Connect timed out"), true),
234+
Arguments.of(new SocketTimeoutException("connect timed out"), true),
235+
Arguments.of(new SocketTimeoutException("timeout"), true),
236+
Arguments.of(new SocketTimeoutException("Read timed out"), true),
237+
Arguments.of(new SocketTimeoutException(), true),
238+
// Should retry on UnknownHostExceptions
239+
Arguments.of(new UnknownHostException("host"), true),
240+
// Should retry on ConnectException
241+
Arguments.of(
242+
new ConnectException("Failed to connect to localhost/[0:0:0:0:0:0:0:1]:62611"), true),
243+
// Shouldn't retry other IOException
244+
Arguments.of(new IOException("error"), false),
245+
// Testing configured predicate
246+
Arguments.of(new HttpRetryException("error", 400), false),
247+
Arguments.of(new HttpRetryException("timeout retry", 400), true));
240248
}
241249

242250
@Test

sdk/common/src/main/java/io/opentelemetry/sdk/common/export/RetryPolicy.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ public static RetryPolicyBuilder builder() {
7070
public abstract double getBackoffMultiplier();
7171

7272
/**
73-
* Returns the predicate used to determine if thrown exception is retryableor {@code null} if no
74-
* predicate was set.
73+
* Returns the predicate used to determine if an attempt which failed exceptionally should be
74+
* retried, or {@code null} if the exporter specific default predicate should be used.
7575
*/
7676
@Nullable
7777
public abstract Predicate<IOException> getRetryExceptionPredicate();
@@ -106,7 +106,10 @@ public abstract static class RetryPolicyBuilder {
106106
*/
107107
public abstract RetryPolicyBuilder setBackoffMultiplier(double backoffMultiplier);
108108

109-
/** Set the predicate to determine if retry should happen based on exception. */
109+
/**
110+
* Set the predicate used to determine if an attempt which failed exceptionally should be
111+
* retried. By default, an exporter specific default predicate should be used.
112+
*/
110113
public abstract RetryPolicyBuilder setRetryExceptionPredicate(
111114
Predicate<IOException> retryExceptionPredicate);
112115

0 commit comments

Comments
 (0)