24
24
import io .opentelemetry .sdk .common .export .RetryPolicy ;
25
25
import java .io .IOException ;
26
26
import java .net .ConnectException ;
27
+ import java .net .HttpRetryException ;
27
28
import java .net .ServerSocket ;
28
29
import java .net .SocketTimeoutException ;
29
30
import java .time .Duration ;
30
31
import java .util .concurrent .TimeUnit ;
31
- import java .util .function .Function ;
32
+ import java .util .function .Predicate ;
33
+ import java .util .logging .Level ;
34
+ import java .util .logging .Logger ;
32
35
import okhttp3 .OkHttpClient ;
33
36
import okhttp3 .Request ;
34
37
import okhttp3 .Response ;
@@ -48,34 +51,38 @@ class RetryInterceptorTest {
48
51
49
52
@ Mock private RetryInterceptor .Sleeper sleeper ;
50
53
@ Mock private RetryInterceptor .BoundedLongGenerator random ;
51
- private Function <IOException , Boolean > isRetryableException ;
54
+ private Predicate <IOException > retryExceptionPredicate ;
52
55
53
56
private RetryInterceptor retrier ;
54
57
private OkHttpClient client ;
55
58
56
59
@ BeforeEach
57
60
void setUp () {
58
- // Note: cannot replace this with lambda or method reference because we need to spy on it
59
- isRetryableException =
61
+ Logger logger = java .util .logging .Logger .getLogger (RetryInterceptor .class .getName ());
62
+ logger .setLevel (Level .FINER );
63
+ retryExceptionPredicate =
60
64
spy (
61
- new Function <IOException , Boolean >() {
65
+ new Predicate <IOException >() {
62
66
@ Override
63
- public Boolean apply (IOException exception ) {
64
- return RetryInterceptor .isRetryableException (exception );
67
+ public boolean test (IOException e ) {
68
+ return RetryInterceptor .isRetryableException (e )
69
+ || (e instanceof HttpRetryException
70
+ && e .getMessage ().contains ("timeout retry" ));
65
71
}
66
72
});
73
+
74
+ RetryPolicy retryPolicy =
75
+ RetryPolicy .builder ()
76
+ .setBackoffMultiplier (1.6 )
77
+ .setInitialBackoff (Duration .ofSeconds (1 ))
78
+ .setMaxBackoff (Duration .ofSeconds (2 ))
79
+ .setMaxAttempts (5 )
80
+ .setRetryExceptionPredicate (retryExceptionPredicate )
81
+ .build ();
82
+
67
83
retrier =
68
84
new RetryInterceptor (
69
- RetryPolicy .builder ()
70
- .setBackoffMultiplier (1.6 )
71
- .setInitialBackoff (Duration .ofSeconds (1 ))
72
- .setMaxBackoff (Duration .ofSeconds (2 ))
73
- .setMaxAttempts (5 )
74
- .build (),
75
- r -> !r .isSuccessful (),
76
- isRetryableException ,
77
- sleeper ,
78
- random );
85
+ retryPolicy , r -> !r .isSuccessful (), retryExceptionPredicate , sleeper , random );
79
86
client = new OkHttpClient .Builder ().addInterceptor (retrier ).build ();
80
87
}
81
88
@@ -154,7 +161,7 @@ void connectTimeout() throws Exception {
154
161
client .newCall (new Request .Builder ().url ("http://10.255.255.1" ).build ()).execute ())
155
162
.isInstanceOf (SocketTimeoutException .class );
156
163
157
- verify (isRetryableException , times (5 )).apply (any ());
164
+ verify (retryExceptionPredicate , times (5 )).test (any ());
158
165
// Should retry maxAttempts, and sleep maxAttempts - 1 times
159
166
verify (sleeper , times (4 )).sleep (anyLong ());
160
167
}
@@ -174,7 +181,7 @@ void connectException() throws Exception {
174
181
.execute ())
175
182
.isInstanceOfAny (ConnectException .class , SocketTimeoutException .class );
176
183
177
- verify (isRetryableException , times (5 )).apply (any ());
184
+ verify (retryExceptionPredicate , times (5 )).test (any ());
178
185
// Should retry maxAttempts, and sleep maxAttempts - 1 times
179
186
verify (sleeper , times (4 )).sleep (anyLong ());
180
187
}
@@ -190,16 +197,16 @@ private static int freePort() {
190
197
@ Test
191
198
void nonRetryableException () throws InterruptedException {
192
199
client = connectTimeoutClient ();
193
- // Override isRetryableException so that no exception is retryable
194
- when (isRetryableException . apply (any ())).thenReturn (false );
200
+ // Override retryPredicate so that no exception is retryable
201
+ when (retryExceptionPredicate . test (any ())).thenReturn (false );
195
202
196
203
// Connecting to a non-routable IP address to trigger connection timeout
197
204
assertThatThrownBy (
198
205
() ->
199
206
client .newCall (new Request .Builder ().url ("http://10.255.255.1" ).build ()).execute ())
200
207
.isInstanceOf (SocketTimeoutException .class );
201
208
202
- verify (isRetryableException , times (1 )).apply (any ());
209
+ verify (retryExceptionPredicate , times (1 )).test (any ());
203
210
verify (sleeper , never ()).sleep (anyLong ());
204
211
}
205
212
@@ -214,20 +221,51 @@ private OkHttpClient connectTimeoutClient() {
214
221
void isRetryableException () {
215
222
// Should retry on connection timeouts, where error message is "Connect timed out" or "connect
216
223
// timed out"
217
- assertThat (
218
- RetryInterceptor .isRetryableException (new SocketTimeoutException ("Connect timed out" )))
224
+ assertThat (retrier .shouldRetryOnException (new SocketTimeoutException ("Connect timed out" )))
219
225
.isTrue ();
220
- assertThat (
221
- RetryInterceptor .isRetryableException (new SocketTimeoutException ("connect timed out" )))
226
+ assertThat (retrier .shouldRetryOnException (new SocketTimeoutException ("connect timed out" )))
222
227
.isTrue ();
223
228
// Shouldn't retry on read timeouts, where error message is "Read timed out"
224
- assertThat (RetryInterceptor . isRetryableException (new SocketTimeoutException ("Read timed out" )))
229
+ assertThat (retrier . shouldRetryOnException (new SocketTimeoutException ("Read timed out" )))
225
230
.isFalse ();
226
- // Shouldn't retry on write timeouts, where error message is "timeout", or other IOException
227
- assertThat (RetryInterceptor .isRetryableException (new SocketTimeoutException ("timeout" )))
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 ();
240
+ }
241
+
242
+ @ Test
243
+ void isRetryableExceptionDefaultBehaviour () {
244
+ RetryInterceptor retryInterceptor =
245
+ new RetryInterceptor (RetryPolicy .getDefault (), OkHttpHttpSender ::isRetryable );
246
+ assertThat (
247
+ retryInterceptor .shouldRetryOnException (
248
+ new SocketTimeoutException ("Connect timed out" )))
249
+ .isTrue ();
250
+ assertThat (retryInterceptor .shouldRetryOnException (new IOException ("Connect timed out" )))
251
+ .isFalse ();
252
+ }
253
+
254
+ @ Test
255
+ void isRetryableExceptionCustomRetryPredicate () {
256
+ RetryInterceptor retryInterceptor =
257
+ new RetryInterceptor (
258
+ RetryPolicy .builder ()
259
+ .setRetryExceptionPredicate ((IOException e ) -> e .getMessage ().equals ("retry" ))
260
+ .build (),
261
+ OkHttpHttpSender ::isRetryable );
262
+
263
+ assertThat (retryInterceptor .shouldRetryOnException (new IOException ("some message" ))).isFalse ();
264
+ assertThat (retryInterceptor .shouldRetryOnException (new IOException ("retry" ))).isTrue ();
265
+ assertThat (
266
+ retryInterceptor .shouldRetryOnException (
267
+ new SocketTimeoutException ("Connect timed out" )))
228
268
.isFalse ();
229
- assertThat (RetryInterceptor .isRetryableException (new SocketTimeoutException ())).isTrue ();
230
- assertThat (RetryInterceptor .isRetryableException (new IOException ("error" ))).isFalse ();
231
269
}
232
270
233
271
private Response sendRequest () throws IOException {
0 commit comments