Skip to content

Commit 17c1ee4

Browse files
committed
Defer to timing-related exception in createNextAttempt instead of shouldRetry
1 parent ddbfe1d commit 17c1ee4

3 files changed

Lines changed: 68 additions & 42 deletions

File tree

sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/retrying/RetryAlgorithm.java

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -153,19 +153,25 @@ public TimedAttemptSettings createNextAttempt(
153153
Throwable previousThrowable,
154154
ResponseT previousResponse,
155155
TimedAttemptSettings previousSettings) {
156-
// a small optimization that avoids calling relatively heavy methods
157-
// like timedAlgorithm.createNextAttempt(), when it is not necessary.
158-
if (!shouldRetryBasedOnResult(context, previousThrowable, previousResponse)) {
159-
return null;
156+
if (shouldRetryBasedOnResult(context, previousThrowable, previousResponse)) {
157+
TimedAttemptSettings newSettings =
158+
createNextAttemptBasedOnResult(
159+
context, previousThrowable, previousResponse, previousSettings);
160+
if (newSettings == null) {
161+
newSettings = createNextAttemptBasedOnTiming(context, previousSettings);
162+
}
163+
return newSettings;
160164
}
161-
162-
TimedAttemptSettings newSettings =
163-
createNextAttemptBasedOnResult(
164-
context, previousThrowable, previousResponse, previousSettings);
165-
if (newSettings == null) {
166-
newSettings = createNextAttemptBasedOnTiming(context, previousSettings);
165+
// If we shouldn't retry based on result and the last attempt failed with an
166+
// exception, defer to any exception thrown by shouldRetryBasedOnTiming.
167+
// This lets a timeout-related exception get propagated when justified
168+
// rather than e.g. exceptions caused by very short RPC deadlines on a
169+
// final polling attempt.
170+
if (previousThrowable != null) {
171+
TimedAttemptSettings nextSettings = createNextAttemptBasedOnTiming(context, previousSettings);
172+
shouldRetryBasedOnTiming(context, nextSettings);
167173
}
168-
return newSettings;
174+
return null;
169175
}
170176

171177
private TimedAttemptSettings createNextAttemptBasedOnResult(
@@ -232,18 +238,8 @@ public boolean shouldRetry(
232238
ResponseT previousResponse,
233239
TimedAttemptSettings nextAttemptSettings)
234240
throws CancellationException {
235-
boolean retryBasedOnResult =
236-
shouldRetryBasedOnResult(context, previousThrowable, previousResponse);
237-
238-
// Short-circuit when operation has succeeded to avoid erroneously throwing
239-
// CancellationException below
240-
if (!retryBasedOnResult && previousThrowable == null) {
241-
return false;
242-
}
243-
244-
// throws CancellationException
245-
boolean retryBasedOnTiming = shouldRetryBasedOnTiming(context, nextAttemptSettings);
246-
return retryBasedOnResult && retryBasedOnTiming;
241+
return shouldRetryBasedOnResult(context, previousThrowable, previousResponse)
242+
&& shouldRetryBasedOnTiming(context, nextAttemptSettings);
247243
}
248244

249245
boolean shouldRetryBasedOnResult(

sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/retrying/RetryAlgorithmTest.java

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,14 @@
3030
package com.google.api.gax.retrying;
3131

3232
import static org.junit.jupiter.api.Assertions.assertFalse;
33+
import static org.junit.jupiter.api.Assertions.assertNull;
34+
import static org.junit.jupiter.api.Assertions.assertThrows;
3335
import static org.mockito.Mockito.mock;
3436
import static org.mockito.Mockito.never;
3537
import static org.mockito.Mockito.verify;
3638
import static org.mockito.Mockito.when;
3739

40+
import java.util.concurrent.CancellationException;
3841
import org.junit.jupiter.api.Test;
3942

4043
@SuppressWarnings({"unchecked", "deprecation"})
@@ -114,6 +117,45 @@ void testNextAttemptWithContext() {
114117
verify(resultAlgorithm).shouldRetry(context, previousThrowable, previousResult);
115118
}
116119

120+
@Test
121+
void testCreateNextAttempt_exceptionAndTimeout_defersToTimingException() {
122+
ResultRetryAlgorithm<Object> resultAlgorithm = mock(ResultRetryAlgorithm.class);
123+
TimedRetryAlgorithm timedAlgorithm = mock(TimedRetryAlgorithm.class);
124+
RetryAlgorithm<Object> algorithm = new RetryAlgorithm<>(resultAlgorithm, timedAlgorithm);
125+
Throwable throwable = new Throwable();
126+
Object result = new Object();
127+
TimedAttemptSettings previousSettings = mock(TimedAttemptSettings.class);
128+
TimedAttemptSettings nextSettings = mock(TimedAttemptSettings.class);
129+
130+
when(resultAlgorithm.shouldRetry(throwable, result)).thenReturn(false);
131+
when(timedAlgorithm.createNextAttempt(previousSettings)).thenReturn(nextSettings);
132+
when(timedAlgorithm.shouldRetry(nextSettings)).thenThrow(new CancellationException());
133+
134+
assertThrows(
135+
CancellationException.class,
136+
() -> algorithm.createNextAttempt(throwable, result, previousSettings));
137+
}
138+
139+
@Test
140+
void testCreateNextAttempt_exceptionAndNoTimeout_returnsNull() {
141+
ResultRetryAlgorithm<Object> resultAlgorithm = mock(ResultRetryAlgorithm.class);
142+
TimedRetryAlgorithm timedAlgorithm = mock(TimedRetryAlgorithm.class);
143+
RetryAlgorithm<Object> algorithm = new RetryAlgorithm<>(resultAlgorithm, timedAlgorithm);
144+
Throwable throwable = new Throwable();
145+
Object result = new Object();
146+
TimedAttemptSettings previousSettings = mock(TimedAttemptSettings.class);
147+
TimedAttemptSettings nextSettings = mock(TimedAttemptSettings.class);
148+
149+
when(resultAlgorithm.shouldRetry(throwable, result)).thenReturn(false);
150+
when(timedAlgorithm.createNextAttempt(previousSettings)).thenReturn(nextSettings);
151+
when(timedAlgorithm.shouldRetry(nextSettings)).thenReturn(true);
152+
153+
TimedAttemptSettings nextAttemptSettings =
154+
algorithm.createNextAttempt(throwable, result, previousSettings);
155+
156+
assertNull(nextAttemptSettings);
157+
}
158+
117159
@Test
118160
void testShouldRetry() {
119161
ResultRetryAlgorithm<Object> resultAlgorithm = mock(ResultRetryAlgorithm.class);
@@ -203,23 +245,6 @@ void testShouldRetryWithContext_noPreviousSettings() {
203245
assertFalse(algorithm.shouldRetry(context, previousThrowable, previousResult, null));
204246
}
205247

206-
@Test
207-
void testShouldRetry_resultFalseAndThrowableNotNull_callsTimed() {
208-
ResultRetryAlgorithm<Object> resultAlgorithm = mock(ResultRetryAlgorithm.class);
209-
TimedRetryAlgorithm timedAlgorithm = mock(TimedRetryAlgorithm.class);
210-
RetryAlgorithm<Object> algorithm = new RetryAlgorithm<>(resultAlgorithm, timedAlgorithm);
211-
Throwable previousThrowable = new Throwable();
212-
Object previousResult = new Object();
213-
TimedAttemptSettings previousSettings = mock(TimedAttemptSettings.class);
214-
when(resultAlgorithm.shouldRetry(previousThrowable, previousResult)).thenReturn(false);
215-
216-
boolean shouldRetry =
217-
algorithm.shouldRetry(previousThrowable, previousResult, previousSettings);
218-
219-
assertFalse(shouldRetry);
220-
verify(timedAlgorithm).shouldRetry(previousSettings);
221-
}
222-
223248
@Test
224249
void testShouldRetry_resultFalseAndThrowableNull_shortCircuits() {
225250
ResultRetryAlgorithm<Object> resultAlgorithm = mock(ResultRetryAlgorithm.class);

sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/StreamingRetryAlgorithmTest.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,17 @@ void testNextAttemptReturnsNullWhenShouldNotRetry() {
116116
StreamingRetryAlgorithm<String> algorithm =
117117
new StreamingRetryAlgorithm<>(resultAlgorithm, timedAlgorithm);
118118

119+
TimedAttemptSettings previousSettings = mock(TimedAttemptSettings.class);
120+
when(previousSettings.getGlobalSettings()).thenReturn(DEFAULT_RETRY_SETTINGS);
121+
when(previousSettings.getRpcTimeoutDuration())
122+
.thenReturn(DEFAULT_RETRY_SETTINGS.getInitialRpcTimeoutDuration());
123+
119124
TimedAttemptSettings attempt =
120-
algorithm.createNextAttempt(context, exception, null, mock(TimedAttemptSettings.class));
125+
algorithm.createNextAttempt(context, exception, null, previousSettings);
121126
assertThat(attempt).isNull();
122127

123128
TimedAttemptSettings attemptWithoutContext =
124-
algorithm.createNextAttempt(exception, null, mock(TimedAttemptSettings.class));
129+
algorithm.createNextAttempt(exception, null, previousSettings);
125130
assertThat(attemptWithoutContext).isNull();
126131
}
127132

0 commit comments

Comments
 (0)