Skip to content

Commit 3d2520d

Browse files
committed
chore: bring retry behavior inline with RetryHelper
Add test to ensure same number of attempts for the same retry settings.
1 parent 3e38109 commit 3d2520d

File tree

2 files changed

+81
-8
lines changed

2 files changed

+81
-8
lines changed

google-cloud-storage/src/main/java/com/google/cloud/storage/RetryContext.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ <T extends Throwable> void recordError(T t, OnSuccess onSuccess, OnFailure<T> on
129129
maxAttempts = Integer.MAX_VALUE;
130130
}
131131
boolean shouldRetry = algorithm.shouldRetry(t, null);
132-
Duration elapsedOverall = backoff.getCumulativeBackoff().plus(elapsed);
132+
Duration cumulativeBackoff = backoff.getCumulativeBackoff();
133133
BackoffResult nextBackoff = backoff.nextBackoff(elapsed);
134134
String msgPrefix = null;
135135
if (shouldRetry && failureCount >= maxAttempts) {
@@ -167,7 +167,7 @@ <T extends Throwable> void recordError(T t, OnSuccess onSuccess, OnFailure<T> on
167167
maxAttempts == Integer.MAX_VALUE
168168
? ""
169169
: String.format(", maxAttempts: %d", maxAttempts),
170-
elapsedOverall,
170+
cumulativeBackoff,
171171
nextBackoff.errorString(),
172172
Durations.eq(backoff.getTimeout(), Durations.EFFECTIVE_INFINITY)
173173
? ""
@@ -291,6 +291,7 @@ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
291291
return new DirectScheduledFuture(unit, delay, command);
292292
}
293293

294+
// <editor-fold desc="UnsupportedOperations">
294295
@Override
295296
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
296297
throw new UnsupportedOperationException();
@@ -377,6 +378,7 @@ public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, Ti
377378
public void execute(Runnable command) {
378379
throw new UnsupportedOperationException();
379380
}
381+
// </editor-fold>
380382

381383
private final class DirectScheduledFuture implements ScheduledFuture<Object> {
382384

google-cloud-storage/src/test/java/com/google/cloud/storage/RetryContextTest.java

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,28 @@
2323
import com.google.api.core.NanoClock;
2424
import com.google.api.gax.grpc.GrpcStatusCode;
2525
import com.google.api.gax.retrying.BasicResultRetryAlgorithm;
26+
import com.google.api.gax.retrying.ResultRetryAlgorithm;
2627
import com.google.api.gax.retrying.RetrySettings;
2728
import com.google.api.gax.rpc.ApiException;
2829
import com.google.api.gax.rpc.ApiExceptionFactory;
2930
import com.google.api.gax.rpc.ResourceExhaustedException;
31+
import com.google.cloud.RetryHelper;
32+
import com.google.cloud.RetryHelper.RetryHelperException;
3033
import com.google.cloud.storage.Backoff.Jitterer;
3134
import com.google.cloud.storage.RetryContext.OnFailure;
3235
import com.google.cloud.storage.RetryContext.OnSuccess;
3336
import com.google.cloud.storage.Retrying.RetryingDependencies;
37+
import com.google.common.base.Stopwatch;
3438
import io.grpc.Status.Code;
3539
import java.time.Duration;
40+
import java.util.ArrayList;
3641
import java.util.Arrays;
3742
import java.util.List;
3843
import java.util.concurrent.CountDownLatch;
3944
import java.util.concurrent.Executors;
4045
import java.util.concurrent.ScheduledExecutorService;
4146
import java.util.concurrent.TimeUnit;
47+
import java.util.concurrent.atomic.AtomicBoolean;
4248
import java.util.concurrent.atomic.AtomicReference;
4349
import java.util.stream.Collectors;
4450
import 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

Comments
 (0)