Skip to content

Commit 3f9f74a

Browse files
authored
feat: retry admin request limit exceeded error (#669)
* feat: retry admin request limit exceeded error Automatically retry requests that fail because the admin requests per seconds limit has been exceeded using an exponential backoff. Fixes #655 and others * fix: remove unused variable * fix: extract strings to constants
1 parent 0c30632 commit 3f9f74a

File tree

4 files changed

+384
-103
lines changed

4 files changed

+384
-103
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.spanner;
18+
19+
import javax.annotation.Nullable;
20+
21+
/**
22+
* Exception thrown by Cloud Spanner the number of administrative requests per minute has been
23+
* exceeded.
24+
*/
25+
public class AdminRequestsPerMinuteExceededException extends SpannerException {
26+
private static final long serialVersionUID = -6395746612598975751L;
27+
28+
static final String ADMIN_REQUESTS_LIMIT_KEY = "quota_limit";
29+
static final String ADMIN_REQUESTS_LIMIT_VALUE = "AdminMethodQuotaPerMinutePerProject";
30+
31+
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
32+
AdminRequestsPerMinuteExceededException(
33+
DoNotConstructDirectly token, @Nullable String message, @Nullable Throwable cause) {
34+
super(token, ErrorCode.RESOURCE_EXHAUSTED, true, message, cause);
35+
}
36+
}

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.google.cloud.spanner.SpannerException.DoNotConstructDirectly;
2323
import com.google.common.base.MoreObjects;
2424
import com.google.common.base.Predicate;
25+
import com.google.rpc.ErrorInfo;
2526
import com.google.rpc.ResourceInfo;
2627
import io.grpc.Context;
2728
import io.grpc.Metadata;
@@ -46,6 +47,8 @@ public final class SpannerExceptionFactory {
4647
"type.googleapis.com/google.spanner.admin.instance.v1.Instance";
4748
private static final Metadata.Key<ResourceInfo> KEY_RESOURCE_INFO =
4849
ProtoUtils.keyForProto(ResourceInfo.getDefaultInstance());
50+
private static final Metadata.Key<ErrorInfo> KEY_ERROR_INFO =
51+
ProtoUtils.keyForProto(ErrorInfo.getDefaultInstance());
4952

5053
public static SpannerException newSpannerException(ErrorCode code, @Nullable String message) {
5154
return newSpannerException(code, message, null);
@@ -213,13 +216,33 @@ private static ResourceInfo extractResourceInfo(Throwable cause) {
213216
return null;
214217
}
215218

219+
private static ErrorInfo extractErrorInfo(Throwable cause) {
220+
if (cause != null) {
221+
Metadata trailers = Status.trailersFromThrowable(cause);
222+
if (trailers != null) {
223+
return trailers.get(KEY_ERROR_INFO);
224+
}
225+
}
226+
return null;
227+
}
228+
216229
static SpannerException newSpannerExceptionPreformatted(
217230
ErrorCode code, @Nullable String message, @Nullable Throwable cause) {
218231
// This is the one place in the codebase that is allowed to call constructors directly.
219232
DoNotConstructDirectly token = DoNotConstructDirectly.ALLOWED;
220233
switch (code) {
221234
case ABORTED:
222235
return new AbortedException(token, message, cause);
236+
case RESOURCE_EXHAUSTED:
237+
ErrorInfo info = extractErrorInfo(cause);
238+
if (info != null
239+
&& info.getMetadataMap()
240+
.containsKey(AdminRequestsPerMinuteExceededException.ADMIN_REQUESTS_LIMIT_KEY)
241+
&& AdminRequestsPerMinuteExceededException.ADMIN_REQUESTS_LIMIT_VALUE.equals(
242+
info.getMetadataMap()
243+
.get(AdminRequestsPerMinuteExceededException.ADMIN_REQUESTS_LIMIT_KEY))) {
244+
return new AdminRequestsPerMinuteExceededException(token, message, cause);
245+
}
223246
case NOT_FOUND:
224247
ResourceInfo resourceInfo = extractResourceInfo(cause);
225248
if (resourceInfo != null) {

0 commit comments

Comments
 (0)