Skip to content
This repository was archived by the owner on Sep 26, 2023. It is now read-only.

Commit 5509053

Browse files
committed
Expose Status Code in ApiException
Added getStatusCode() method to ApiException to return status code Added additional tests Pre-push hook installed. Change-Id: I89abe3228be5d2579453d8b8e5d76b27e8300a40
1 parent fc8e8ba commit 5509053

3 files changed

Lines changed: 100 additions & 5 deletions

File tree

src/main/java/com/google/api/gax/grpc/ApiException.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,22 @@
3131

3232
package com.google.api.gax.grpc;
3333

34+
import com.google.common.base.Preconditions;
35+
36+
import io.grpc.Status;
37+
3438
/**
3539
* Represents an exception thrown during an RPC call.
3640
*
3741
* It stores information useful for functionalities in {@link ApiCallable}.
3842
*/
3943
public class ApiException extends RuntimeException {
4044
private final boolean retryable;
45+
private final Status.Code statusCode;
4146

42-
ApiException(Throwable cause, boolean retryable) {
47+
ApiException(Throwable cause, Status.Code statusCode, boolean retryable) {
4348
super(cause);
49+
this.statusCode = Preconditions.checkNotNull(statusCode);
4450
this.retryable = retryable;
4551
}
4652

@@ -50,4 +56,13 @@ public class ApiException extends RuntimeException {
5056
public boolean isRetryable() {
5157
return retryable;
5258
}
59+
60+
/**
61+
* Returns the status code of the underlying grpc exception. In cases
62+
* where the underlying exception is not of type StatusException or
63+
* StatusRuntimeException, the status code will be Status.Code.UNKNOWN
64+
*/
65+
public Status.Code getStatusCode() {
66+
return statusCode;
67+
}
5368
}

src/main/java/com/google/api/gax/grpc/ExceptionTransformingCallable.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,22 @@ public void onSuccess(ResponseT r) {
7070

7171
@Override
7272
public void onFailure(Throwable throwable) {
73-
boolean canRetry = false;
73+
Status.Code statusCode;
74+
boolean canRetry;
7475
if (throwable instanceof StatusException) {
7576
StatusException e = (StatusException) throwable;
76-
canRetry = retryableCodes.contains(e.getStatus().getCode());
77+
statusCode = e.getStatus().getCode();
78+
canRetry = retryableCodes.contains(statusCode);
7779
} else if (throwable instanceof StatusRuntimeException) {
7880
StatusRuntimeException e = (StatusRuntimeException) throwable;
79-
canRetry = retryableCodes.contains(e.getStatus().getCode());
81+
statusCode = e.getStatus().getCode();
82+
canRetry = retryableCodes.contains(statusCode);
83+
} else {
84+
// Do not retry on unknown throwable, even when UNKNOWN is in retryableCodes
85+
statusCode = Status.Code.UNKNOWN;
86+
canRetry = false;
8087
}
81-
result.setException(new ApiException(throwable, canRetry));
88+
result.setException(new ApiException(throwable, statusCode, canRetry));
8289
}
8390
});
8491
return result;

src/test/java/com/google/api/gax/grpc/ApiCallableTest.java

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,37 @@ public void retry() {
146146
Truth.assertThat(callable.call(1)).isEqualTo(2);
147147
}
148148

149+
@Test
150+
public void retryOnStatusUnknown() {
151+
ImmutableSet<Status.Code> retryable = ImmutableSet.<Status.Code>of(Status.Code.UNKNOWN);
152+
Throwable t = Status.UNKNOWN.asException();
153+
Mockito.when(callInt.futureCall((CallContext<Integer>)Mockito.any()))
154+
.thenReturn(Futures.<Integer>immediateFailedFuture(t))
155+
.thenReturn(Futures.<Integer>immediateFailedFuture(t))
156+
.thenReturn(Futures.<Integer>immediateFailedFuture(t))
157+
.thenReturn(Futures.<Integer>immediateFuture(2));
158+
ApiCallable<Integer, Integer> callable =
159+
ApiCallable.<Integer, Integer>create(callInt)
160+
.retryableOn(retryable)
161+
.retrying(testRetryParams, EXECUTOR, new FakeNanoClock(System.nanoTime()));
162+
Truth.assertThat(callable.call(1)).isEqualTo(2);
163+
}
164+
165+
@Test
166+
public void retryOnUnexpectedException() {
167+
thrown.expect(UncheckedExecutionException.class);
168+
thrown.expectMessage("foobar");
169+
ImmutableSet<Status.Code> retryable = ImmutableSet.<Status.Code>of(Status.Code.UNKNOWN);
170+
Throwable t = new RuntimeException("foobar");
171+
Mockito.when(callInt.futureCall((CallContext<Integer>)Mockito.any()))
172+
.thenReturn(Futures.<Integer>immediateFailedFuture(t));
173+
ApiCallable<Integer, Integer> callable =
174+
ApiCallable.<Integer, Integer>create(callInt)
175+
.retryableOn(retryable)
176+
.retrying(testRetryParams, EXECUTOR, new FakeNanoClock(System.nanoTime()));
177+
callable.call(1);
178+
}
179+
149180
@Test
150181
public void retryNoRecover() {
151182
thrown.expect(UncheckedExecutionException.class);
@@ -396,4 +427,46 @@ public void bundlingException() throws Exception {
396427
bundlerFactory.close();
397428
}
398429
}
430+
431+
// ApiException
432+
// ============
433+
434+
@Test
435+
public void testKnownStatusCode() {
436+
ImmutableSet<Status.Code> retryable = ImmutableSet.<Status.Code>of(Status.Code.UNAVAILABLE);
437+
Mockito.when(callInt.futureCall((CallContext<Integer>)Mockito.any()))
438+
.thenReturn(
439+
Futures.<Integer>immediateFailedFuture(
440+
Status.FAILED_PRECONDITION.withDescription("known").asException()));
441+
ApiCallable<Integer, Integer> callable =
442+
ApiCallable.<Integer, Integer>create(callInt)
443+
.retryableOn(retryable);
444+
try {
445+
callable.call(1);
446+
} catch (UncheckedExecutionException exception) {
447+
ApiException apiException = (ApiException) exception.getCause();
448+
Truth.assertThat(apiException.getStatusCode()).isEqualTo(Status.Code.FAILED_PRECONDITION);
449+
Truth.assertThat(apiException.getMessage()).isEqualTo(
450+
"io.grpc.StatusException: FAILED_PRECONDITION: known");
451+
}
452+
}
453+
454+
@Test
455+
public void testUnknownStatusCode() {
456+
ImmutableSet<Status.Code> retryable = ImmutableSet.<Status.Code>of();
457+
Mockito.when(callInt.futureCall((CallContext<Integer>)Mockito.any()))
458+
.thenReturn(
459+
Futures.<Integer>immediateFailedFuture(
460+
new RuntimeException("unknown")));
461+
ApiCallable<Integer, Integer> callable =
462+
ApiCallable.<Integer, Integer>create(callInt)
463+
.retryableOn(retryable);
464+
try {
465+
callable.call(1);
466+
} catch (UncheckedExecutionException exception) {
467+
ApiException apiException = (ApiException) exception.getCause();
468+
Truth.assertThat(apiException.getStatusCode()).isEqualTo(Status.Code.UNKNOWN);
469+
Truth.assertThat(apiException.getMessage()).isEqualTo("java.lang.RuntimeException: unknown");
470+
}
471+
}
399472
}

0 commit comments

Comments
 (0)