Skip to content

Commit 72f7ae2

Browse files
chore(spanner): extract ErrorDetails from grpc-status-details-bin for raw gRPC exceptions
1 parent 30956a3 commit 72f7ae2

3 files changed

Lines changed: 66 additions & 6 deletions

File tree

java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerException.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,21 @@ enum DoNotConstructDirectly {
137137
* retry delay.
138138
*/
139139
public long getRetryDelayInMillis() {
140-
return extractRetryDelay(this.getCause());
140+
return extractRetryDelay(this, this.apiException);
141141
}
142142

143143
static long extractRetryDelay(Throwable cause) {
144+
return extractRetryDelay(cause, null);
145+
}
146+
147+
static long extractRetryDelay(Throwable cause, @Nullable ApiException apiException) {
148+
ErrorDetails details = SpannerExceptionFactory.extractErrorDetails(cause, apiException);
149+
if (details != null && details.getRetryInfo() != null) {
150+
RetryInfo retryInfo = details.getRetryInfo();
151+
if (retryInfo.hasRetryDelay()) {
152+
return Durations.toMillis(retryInfo.getRetryDelay());
153+
}
154+
}
144155
if (cause != null) {
145156
Metadata trailers = Status.trailersFromThrowable(cause);
146157
if (trailers != null && trailers.containsKey(KEY_RETRY_INFO)) {
@@ -227,7 +238,7 @@ public ErrorDetails getErrorDetails() {
227238
if (this.apiException != null) {
228239
return this.apiException.getErrorDetails();
229240
}
230-
return null;
241+
return SpannerExceptionFactory.extractErrorDetails(getCause(), null);
231242
}
232243

233244
void setStatement(String statement) {

java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerExceptionFactory.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.google.cloud.spanner.SpannerException.DoNotConstructDirectly;
2727
import com.google.common.base.MoreObjects;
2828
import com.google.common.base.Predicate;
29+
import com.google.protobuf.InvalidProtocolBufferException;
2930
import com.google.rpc.ErrorInfo;
3031
import com.google.rpc.ResourceInfo;
3132
import com.google.rpc.RetryInfo;
@@ -56,6 +57,8 @@ public final class SpannerExceptionFactory {
5657
ProtoUtils.keyForProto(ResourceInfo.getDefaultInstance());
5758
private static final Metadata.Key<ErrorInfo> KEY_ERROR_INFO =
5859
ProtoUtils.keyForProto(ErrorInfo.getDefaultInstance());
60+
private static final Metadata.Key<byte[]> KEY_ERROR_DETAILS =
61+
Metadata.Key.of("grpc-status-details-bin", Metadata.BINARY_BYTE_MARSHALLER);
5962

6063
public static SpannerException newSpannerException(ErrorCode code, @Nullable String message) {
6164
return newSpannerException(code, message, null);
@@ -247,6 +250,10 @@ private static String formatMessage(ErrorCode code, @Nullable String message) {
247250
}
248251

249252
private static ResourceInfo extractResourceInfo(Throwable cause) {
253+
ErrorDetails details = extractErrorDetails(cause, null);
254+
if (details != null && details.getResourceInfo() != null) {
255+
return details.getResourceInfo();
256+
}
250257
if (cause != null) {
251258
Metadata trailers = Status.trailersFromThrowable(cause);
252259
if (trailers != null) {
@@ -257,8 +264,9 @@ private static ResourceInfo extractResourceInfo(Throwable cause) {
257264
}
258265

259266
private static ErrorInfo extractErrorInfo(Throwable cause, ApiException apiException) {
260-
if (apiException != null && apiException.getErrorDetails() != null) {
261-
return apiException.getErrorDetails().getErrorInfo();
267+
ErrorDetails details = extractErrorDetails(cause, apiException);
268+
if (details != null && details.getErrorInfo() != null) {
269+
return details.getErrorInfo();
262270
}
263271
if (cause != null) {
264272
Metadata trailers = Status.trailersFromThrowable(cause);
@@ -277,10 +285,26 @@ static ErrorDetails extractErrorDetails(Throwable cause, ApiException apiExcepti
277285
Throwable prevCause = null;
278286
while (cause != null && cause != prevCause) {
279287
if (cause instanceof ApiException) {
280-
return ((ApiException) cause).getErrorDetails();
288+
if (((ApiException) cause).getErrorDetails() != null) {
289+
return ((ApiException) cause).getErrorDetails();
290+
}
281291
}
282292
if (cause instanceof SpannerException) {
283-
return ((SpannerException) cause).getErrorDetails();
293+
if (((SpannerException) cause).getErrorDetails() != null) {
294+
return ((SpannerException) cause).getErrorDetails();
295+
}
296+
}
297+
Metadata trailers = Status.trailersFromThrowable(cause);
298+
if (trailers != null) {
299+
byte[] bytes = trailers.get(KEY_ERROR_DETAILS);
300+
if (bytes != null) {
301+
try {
302+
com.google.rpc.Status status = com.google.rpc.Status.parseFrom(bytes);
303+
return ErrorDetails.builder().setRawErrorMessages(status.getDetailsList()).build();
304+
} catch (InvalidProtocolBufferException e) {
305+
// ignore and continue
306+
}
307+
}
284308
}
285309
prevCause = cause;
286310
cause = cause.getCause();

java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerExceptionFactoryTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,4 +251,29 @@ private Metadata createResourceTypeMetadata(String resourceType, String resource
251251

252252
return trailers;
253253
}
254+
255+
@Test
256+
public void resourceExhaustedWithGrpcStatusDetails() {
257+
Status status =
258+
Status.fromCodeValue(Status.Code.RESOURCE_EXHAUSTED.value())
259+
.withDescription("Memory pushback");
260+
Metadata trailers = new Metadata();
261+
Metadata.Key<byte[]> key =
262+
Metadata.Key.of("grpc-status-details-bin", Metadata.BINARY_BYTE_MARSHALLER);
263+
RetryInfo retryInfo =
264+
RetryInfo.newBuilder()
265+
.setRetryDelay(Duration.newBuilder().setNanos(1000000).setSeconds(1L))
266+
.build();
267+
com.google.rpc.Status rpcStatus =
268+
com.google.rpc.Status.newBuilder()
269+
.setCode(com.google.rpc.Code.RESOURCE_EXHAUSTED_VALUE)
270+
.setMessage("Memory pushback")
271+
.addDetails(com.google.protobuf.Any.pack(retryInfo))
272+
.build();
273+
trailers.put(key, rpcStatus.toByteArray());
274+
SpannerException e =
275+
SpannerExceptionFactory.newSpannerException(new StatusRuntimeException(status, trailers));
276+
assertThat(e.isRetryable()).isTrue();
277+
assertThat(e.getRetryDelayInMillis()).isEqualTo(1001);
278+
}
254279
}

0 commit comments

Comments
 (0)