Skip to content

Commit 996f2ac

Browse files
committed
fix: stop sending RPCs on InstanceNotFound
The session pool should handle InstanceNotFound in the same way as DatabaseNotFound errors, i.e. stop sending RPCs and also consider an instance that has been re-created with the same name as a different instance, and hence require the user to re-create any database client before it can be used again. Fixes #60
1 parent 89c3e77 commit 996f2ac

File tree

6 files changed

+361
-205
lines changed

6 files changed

+361
-205
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 com.google.cloud.spanner.SpannerException.ResourceNotFoundException;
20+
import com.google.rpc.ResourceInfo;
21+
import javax.annotation.Nullable;
22+
23+
/**
24+
* Exception thrown by Cloud Spanner when an operation detects that the instance that is being used
25+
* no longer exists. This type of error has its own subclass as it is a condition that should cause
26+
* the client library to stop trying to send RPCs to the backend until the user has taken action.
27+
*/
28+
public class InstanceNotFoundException extends ResourceNotFoundException {
29+
private static final long serialVersionUID = 45297002L;
30+
31+
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
32+
InstanceNotFoundException(
33+
DoNotConstructDirectly token,
34+
@Nullable String message,
35+
ResourceInfo resourceInfo,
36+
@Nullable Throwable cause) {
37+
super(token, message, resourceInfo, cause);
38+
}
39+
}

Diff for: google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java

+22-19
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.google.cloud.spanner.Options.QueryOption;
2727
import com.google.cloud.spanner.Options.ReadOption;
2828
import com.google.cloud.spanner.SessionClient.SessionConsumer;
29+
import com.google.cloud.spanner.SpannerException.ResourceNotFoundException;
2930
import com.google.common.annotations.VisibleForTesting;
3031
import com.google.common.base.Function;
3132
import com.google.common.base.MoreObjects;
@@ -781,13 +782,14 @@ public void close() {
781782
if (lastException != null && isSessionNotFound(lastException)) {
782783
invalidateSession(this);
783784
} else {
784-
if (lastException != null && isDatabaseNotFound(lastException)) {
785+
if (lastException != null && isDatabaseOrInstanceNotFound(lastException)) {
785786
// Mark this session pool as no longer valid and then release the session into the pool as
786787
// there is nothing we can do with it anyways.
787788
synchronized (lock) {
788-
SessionPool.this.databaseNotFound =
789+
SessionPool.this.resourceNotFoundException =
789790
MoreObjects.firstNonNull(
790-
SessionPool.this.databaseNotFound, (DatabaseNotFoundException) lastException);
791+
SessionPool.this.resourceNotFoundException,
792+
(ResourceNotFoundException) lastException);
791793
}
792794
}
793795
lastException = null;
@@ -1075,7 +1077,7 @@ private static enum Position {
10751077
private SettableFuture<Void> closureFuture;
10761078

10771079
@GuardedBy("lock")
1078-
private DatabaseNotFoundException databaseNotFound;
1080+
private ResourceNotFoundException resourceNotFoundException;
10791081

10801082
@GuardedBy("lock")
10811083
private final LinkedList<PooledSession> readSessions = new LinkedList<>();
@@ -1213,8 +1215,8 @@ private boolean isSessionNotFound(SpannerException e) {
12131215
return e.getErrorCode() == ErrorCode.NOT_FOUND && e.getMessage().contains("Session not found");
12141216
}
12151217

1216-
private boolean isDatabaseNotFound(SpannerException e) {
1217-
return e instanceof DatabaseNotFoundException;
1218+
private boolean isDatabaseOrInstanceNotFound(SpannerException e) {
1219+
return e instanceof DatabaseNotFoundException || e instanceof InstanceNotFoundException;
12181220
}
12191221

12201222
private boolean isPermissionDenied(SpannerException e) {
@@ -1249,7 +1251,7 @@ private PooledSession findSessionToKeepAlive(
12491251
/** @return true if this {@link SessionPool} is still valid. */
12501252
boolean isValid() {
12511253
synchronized (lock) {
1252-
return closureFuture == null && databaseNotFound == null;
1254+
return closureFuture == null && resourceNotFoundException == null;
12531255
}
12541256
}
12551257

@@ -1279,14 +1281,14 @@ PooledSession getReadSession() throws SpannerException {
12791281
span.addAnnotation("Pool has been closed");
12801282
throw new IllegalStateException("Pool has been closed");
12811283
}
1282-
if (databaseNotFound != null) {
1284+
if (resourceNotFoundException != null) {
12831285
span.addAnnotation("Database has been deleted");
12841286
throw SpannerExceptionFactory.newSpannerException(
12851287
ErrorCode.NOT_FOUND,
12861288
String.format(
12871289
"The session pool has been invalidated because a previous RPC returned 'Database not found': %s",
1288-
databaseNotFound.getMessage()),
1289-
databaseNotFound);
1290+
resourceNotFoundException.getMessage()),
1291+
resourceNotFoundException);
12901292
}
12911293
sess = readSessions.poll();
12921294
if (sess == null) {
@@ -1344,14 +1346,14 @@ PooledSession getReadWriteSession() {
13441346
span.addAnnotation("Pool has been closed");
13451347
throw new IllegalStateException("Pool has been closed");
13461348
}
1347-
if (databaseNotFound != null) {
1349+
if (resourceNotFoundException != null) {
13481350
span.addAnnotation("Database has been deleted");
13491351
throw SpannerExceptionFactory.newSpannerException(
13501352
ErrorCode.NOT_FOUND,
13511353
String.format(
13521354
"The session pool has been invalidated because a previous RPC returned 'Database not found': %s",
1353-
databaseNotFound.getMessage()),
1354-
databaseNotFound);
1355+
resourceNotFoundException.getMessage()),
1356+
resourceNotFoundException);
13551357
}
13561358
sess = writePreparedSessions.poll();
13571359
if (sess == null) {
@@ -1495,17 +1497,18 @@ private void handleCreateSessionsFailure(SpannerException e, int count) {
14951497
break;
14961498
}
14971499
}
1498-
this.databaseNotFound =
1500+
this.resourceNotFoundException =
14991501
MoreObjects.firstNonNull(
1500-
this.databaseNotFound, isDatabaseNotFound(e) ? (DatabaseNotFoundException) e : null);
1502+
this.resourceNotFoundException,
1503+
isDatabaseOrInstanceNotFound(e) ? (ResourceNotFoundException) e : null);
15011504
}
15021505
}
15031506

15041507
private void handlePrepareSessionFailure(SpannerException e, PooledSession session) {
15051508
synchronized (lock) {
15061509
if (isSessionNotFound(e)) {
15071510
invalidateSession(session);
1508-
} else if (isDatabaseNotFound(e) || isPermissionDenied(e)) {
1511+
} else if (isDatabaseOrInstanceNotFound(e) || isPermissionDenied(e)) {
15091512
// Database has been deleted or the user has no permission to write to this database. We
15101513
// should stop trying to prepare any transactions. Also propagate the error to all waiters,
15111514
// as any further waiting is pointless.
@@ -1520,10 +1523,10 @@ private void handlePrepareSessionFailure(SpannerException e, PooledSession sessi
15201523
if (isClosed()) {
15211524
decrementPendingClosures(1);
15221525
}
1523-
this.databaseNotFound =
1526+
this.resourceNotFoundException =
15241527
MoreObjects.firstNonNull(
1525-
this.databaseNotFound,
1526-
isDatabaseNotFound(e) ? (DatabaseNotFoundException) e : null);
1528+
this.resourceNotFoundException,
1529+
isDatabaseOrInstanceNotFound(e) ? (ResourceNotFoundException) e : null);
15271530
} else if (readWriteWaiters.size() > 0) {
15281531
releaseSession(session, Position.FIRST);
15291532
readWriteWaiters.poll().put(e);

Diff for: google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerExceptionFactory.java

+4
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public final class SpannerExceptionFactory {
4242
static final String SESSION_RESOURCE_TYPE = "type.googleapis.com/google.spanner.v1.Session";
4343
static final String DATABASE_RESOURCE_TYPE =
4444
"type.googleapis.com/google.spanner.admin.database.v1.Database";
45+
static final String INSTANCE_RESOURCE_TYPE =
46+
"type.googleapis.com/google.spanner.admin.instance.v1.Instance";
4547
private static final Metadata.Key<ResourceInfo> KEY_RESOURCE_INFO =
4648
ProtoUtils.keyForProto(ResourceInfo.getDefaultInstance());
4749

@@ -199,6 +201,8 @@ private static SpannerException newSpannerExceptionPreformatted(
199201
return new SessionNotFoundException(token, message, resourceInfo, cause);
200202
} else if (resourceInfo.getResourceType().equals(DATABASE_RESOURCE_TYPE)) {
201203
return new DatabaseNotFoundException(token, message, resourceInfo, cause);
204+
} else if (resourceInfo.getResourceType().equals(INSTANCE_RESOURCE_TYPE)) {
205+
return new InstanceNotFoundException(token, message, resourceInfo, cause);
202206
}
203207
}
204208
// Fall through to the default.

0 commit comments

Comments
 (0)