2424import io .opentelemetry .sdk .common .export .RetryPolicy ;
2525import java .io .IOException ;
2626import java .net .ConnectException ;
27+ import java .net .HttpRetryException ;
2728import java .net .ServerSocket ;
2829import java .net .SocketTimeoutException ;
2930import java .time .Duration ;
3031import 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 ;
3235import okhttp3 .OkHttpClient ;
3336import okhttp3 .Request ;
3437import okhttp3 .Response ;
@@ -48,34 +51,38 @@ class RetryInterceptorTest {
4851
4952 @ Mock private RetryInterceptor .Sleeper sleeper ;
5053 @ Mock private RetryInterceptor .BoundedLongGenerator random ;
51- private Function <IOException , Boolean > isRetryableException ;
54+ private Predicate <IOException > retryExceptionPredicate ;
5255
5356 private RetryInterceptor retrier ;
5457 private OkHttpClient client ;
5558
5659 @ BeforeEach
5760 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 =
6064 spy (
61- new Function <IOException , Boolean >() {
65+ new Predicate <IOException >() {
6266 @ 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" ));
6571 }
6672 });
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+
6783 retrier =
6884 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 );
7986 client = new OkHttpClient .Builder ().addInterceptor (retrier ).build ();
8087 }
8188
@@ -154,7 +161,7 @@ void connectTimeout() throws Exception {
154161 client .newCall (new Request .Builder ().url ("http://10.255.255.1" ).build ()).execute ())
155162 .isInstanceOf (SocketTimeoutException .class );
156163
157- verify (isRetryableException , times (5 )).apply (any ());
164+ verify (retryExceptionPredicate , times (5 )).test (any ());
158165 // Should retry maxAttempts, and sleep maxAttempts - 1 times
159166 verify (sleeper , times (4 )).sleep (anyLong ());
160167 }
@@ -174,7 +181,7 @@ void connectException() throws Exception {
174181 .execute ())
175182 .isInstanceOfAny (ConnectException .class , SocketTimeoutException .class );
176183
177- verify (isRetryableException , times (5 )).apply (any ());
184+ verify (retryExceptionPredicate , times (5 )).test (any ());
178185 // Should retry maxAttempts, and sleep maxAttempts - 1 times
179186 verify (sleeper , times (4 )).sleep (anyLong ());
180187 }
@@ -190,16 +197,16 @@ private static int freePort() {
190197 @ Test
191198 void nonRetryableException () throws InterruptedException {
192199 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 );
195202
196203 // Connecting to a non-routable IP address to trigger connection timeout
197204 assertThatThrownBy (
198205 () ->
199206 client .newCall (new Request .Builder ().url ("http://10.255.255.1" ).build ()).execute ())
200207 .isInstanceOf (SocketTimeoutException .class );
201208
202- verify (isRetryableException , times (1 )).apply (any ());
209+ verify (retryExceptionPredicate , times (1 )).test (any ());
203210 verify (sleeper , never ()).sleep (anyLong ());
204211 }
205212
@@ -214,20 +221,51 @@ private OkHttpClient connectTimeoutClient() {
214221 void isRetryableException () {
215222 // Should retry on connection timeouts, where error message is "Connect timed out" or "connect
216223 // timed out"
217- assertThat (
218- RetryInterceptor .isRetryableException (new SocketTimeoutException ("Connect timed out" )))
224+ assertThat (retrier .shouldRetryOnException (new SocketTimeoutException ("Connect timed out" )))
219225 .isTrue ();
220- assertThat (
221- RetryInterceptor .isRetryableException (new SocketTimeoutException ("connect timed out" )))
226+ assertThat (retrier .shouldRetryOnException (new SocketTimeoutException ("connect timed out" )))
222227 .isTrue ();
223228 // 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" )))
225230 .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" )))
228268 .isFalse ();
229- assertThat (RetryInterceptor .isRetryableException (new SocketTimeoutException ())).isTrue ();
230- assertThat (RetryInterceptor .isRetryableException (new IOException ("error" ))).isFalse ();
231269 }
232270
233271 private Response sendRequest () throws IOException {
0 commit comments