2323import com .google .api .core .NanoClock ;
2424import com .google .api .gax .grpc .GrpcStatusCode ;
2525import com .google .api .gax .retrying .BasicResultRetryAlgorithm ;
26+ import com .google .api .gax .retrying .ResultRetryAlgorithm ;
2627import com .google .api .gax .retrying .RetrySettings ;
2728import com .google .api .gax .rpc .ApiException ;
2829import com .google .api .gax .rpc .ApiExceptionFactory ;
2930import com .google .api .gax .rpc .ResourceExhaustedException ;
31+ import com .google .cloud .RetryHelper ;
32+ import com .google .cloud .RetryHelper .RetryHelperException ;
3033import com .google .cloud .storage .Backoff .Jitterer ;
3134import com .google .cloud .storage .RetryContext .OnFailure ;
3235import com .google .cloud .storage .RetryContext .OnSuccess ;
3336import com .google .cloud .storage .Retrying .RetryingDependencies ;
37+ import com .google .common .base .Stopwatch ;
3438import io .grpc .Status .Code ;
3539import java .time .Duration ;
40+ import java .util .ArrayList ;
3641import java .util .Arrays ;
3742import java .util .List ;
3843import java .util .concurrent .CountDownLatch ;
3944import java .util .concurrent .Executors ;
4045import java .util .concurrent .ScheduledExecutorService ;
4146import java .util .concurrent .TimeUnit ;
47+ import java .util .concurrent .atomic .AtomicBoolean ;
4248import java .util .concurrent .atomic .AtomicReference ;
4349import java .util .stream .Collectors ;
4450import org .junit .Before ;
@@ -73,7 +79,7 @@ public void retryable_when_maxAttemptBudget_consumed() {
7379 Arrays .stream (suppressed ).map (Throwable ::getMessage ).collect (Collectors .toList ());
7480 assertThat (suppressedMessages )
7581 .containsExactly (
76- "Operation failed to complete within attempt budget (attempts: 1, maxAttempts: 1, elapsed: PT0.001S , nextBackoff: PT3S)" );
82+ "Operation failed to complete within attempt budget (attempts: 1, maxAttempts: 1, elapsed: PT0S , nextBackoff: PT3S)" );
7783 });
7884 }
7985
@@ -110,7 +116,7 @@ public void retryable_maxAttemptBudget_still_available() {
110116 Arrays .stream (suppressed ).map (Throwable ::getMessage ).collect (Collectors .toList ());
111117 assertThat (suppressedMessages )
112118 .containsExactly (
113- "Operation failed to complete within attempt budget (attempts: 3, maxAttempts: 3, elapsed: PT15.003S , nextBackoff: PT3S) previous failures follow in order of occurrence" ,
119+ "Operation failed to complete within attempt budget (attempts: 3, maxAttempts: 3, elapsed: PT9.002S , nextBackoff: PT3S) previous failures follow in order of occurrence" ,
114120 "{unavailable}" ,
115121 "{internal}" );
116122 });
@@ -133,7 +139,7 @@ public void nonretryable_regardlessOfAttemptBudget() {
133139 Arrays .stream (suppressed ).map (Throwable ::getMessage ).collect (Collectors .toList ());
134140 assertThat (suppressedMessages )
135141 .containsExactly (
136- "Unretryable error (attempts: 1, maxAttempts: 3, elapsed: PT0.001S , nextBackoff: PT3S)" );
142+ "Unretryable error (attempts: 1, maxAttempts: 3, elapsed: PT0S , nextBackoff: PT3S)" );
137143 });
138144 }
139145
@@ -167,7 +173,7 @@ public boolean shouldRetry(Throwable previousThrowable, Object previousResponse)
167173 Arrays .stream (suppressed ).map (Throwable ::getMessage ).collect (Collectors .toList ());
168174 assertThat (suppressedMessages )
169175 .containsExactly (
170- "Unretryable error (attempts: 3, maxAttempts: 6, elapsed: PT15.003S , nextBackoff: PT3S) previous failures follow in order of occurrence" ,
176+ "Unretryable error (attempts: 3, maxAttempts: 6, elapsed: PT9.002S , nextBackoff: PT3S) previous failures follow in order of occurrence" ,
171177 "{unavailable}" ,
172178 "{internal}" );
173179 });
@@ -204,7 +210,7 @@ public boolean shouldRetry(Throwable previousThrowable, Object previousResponse)
204210 Arrays .stream (suppressed ).map (Throwable ::getMessage ).collect (Collectors .toList ());
205211 assertThat (suppressedMessages )
206212 .containsExactly (
207- "Unretryable error (attempts: 1, maxAttempts: 6, elapsed: PT9.003S , nextBackoff: PT3S)" );
213+ "Unretryable error (attempts: 1, maxAttempts: 6, elapsed: PT9.002S , nextBackoff: PT3S)" );
208214 });
209215 }
210216
@@ -252,7 +258,7 @@ public void retryable_when_timeoutBudget_consumed() {
252258 Arrays .stream (suppressed ).map (Throwable ::getMessage ).collect (Collectors .toList ());
253259 assertThat (suppressedMessages )
254260 .containsExactly (
255- "Operation failed to complete within backoff budget (attempts: 3, elapsed: PT24S , nextBackoff: EXHAUSTED, timeout: PT24S) previous failures follow in order of occurrence" ,
261+ "Operation failed to complete within backoff budget (attempts: 3, elapsed: PT15S , nextBackoff: EXHAUSTED, timeout: PT24S) previous failures follow in order of occurrence" ,
256262 "{unavailable 1}" ,
257263 "{unavailable 2}" );
258264 });
@@ -315,6 +321,65 @@ public void recordErrorWhileAlreadyInBackoffTruncatesExistingBackoffAndReevaluat
315321 }
316322 }
317323
324+ @ Test
325+ public void similarToRetryingHelper () {
326+ RetrySettings retrySettings =
327+ StorageOptions .getDefaultRetrySettings ()
328+ .toBuilder ()
329+ .setTotalTimeoutDuration (Duration .ofMillis (3_125 ))
330+ .setInitialRetryDelayDuration (Duration .ofNanos (12_500_000 ))
331+ .setRetryDelayMultiplier (2.0 )
332+ .setMaxRetryDelayDuration (Duration .ofSeconds (2 ))
333+ .setMaxAttempts (6 )
334+ .setJittered (false )
335+ .build ();
336+ ResultRetryAlgorithm <?> alg =
337+ new BasicResultRetryAlgorithm <Object >() {
338+ @ Override
339+ public boolean shouldRetry (Throwable previousThrowable , Object previousResponse ) {
340+ return previousThrowable instanceof Invocation ;
341+ }
342+ };
343+ ApiClock clock = NanoClock .getDefaultClock ();
344+
345+ RetryContext ctx =
346+ RetryContext .of (
347+ RetryContext .directScheduledExecutorService (),
348+ RetryingDependencies .simple (clock , retrySettings ),
349+ alg ,
350+ Jitterer .noJitter ());
351+
352+ List <Duration > retryHelperSplits = new ArrayList <>();
353+ Stopwatch retryHelperStopwatch = Stopwatch .createStarted ();
354+ try {
355+ RetryHelper .runWithRetries (
356+ () -> {
357+ retryHelperSplits .add (retryHelperStopwatch .elapsed ());
358+ throw new Invocation ();
359+ },
360+ retrySettings ,
361+ alg ,
362+ clock );
363+ } catch (RetryHelperException ignore ) {
364+ }
365+
366+ List <Duration > retryContextSplits = new ArrayList <>();
367+ Stopwatch retryContextStopwatch = Stopwatch .createStarted ();
368+ ctx .reset ();
369+ AtomicBoolean attemptAgain = new AtomicBoolean (false );
370+ do {
371+ attemptAgain .set (false );
372+ try {
373+ retryContextSplits .add (retryContextStopwatch .elapsed ());
374+ throw new Invocation ();
375+ } catch (Exception e ) {
376+ ctx .recordError (e , () -> attemptAgain .set (true ), noop -> {});
377+ }
378+ } while (attemptAgain .get ());
379+
380+ assertThat (retryContextSplits .size ()).isEqualTo (retryHelperSplits .size ());
381+ }
382+
318383 private static ApiException apiException (Code code , String message ) {
319384 return ApiExceptionFactory .createException (message , null , GrpcStatusCode .of (code ), false );
320385 }
@@ -392,4 +457,10 @@ public void release() {
392457 cdl .countDown ();
393458 }
394459 }
460+
461+ static final class Invocation extends Exception {
462+ private Invocation () {
463+ super ();
464+ }
465+ }
395466}
0 commit comments