Skip to content
This repository was archived by the owner on Apr 7, 2026. It is now read-only.

Commit 34caeb2

Browse files
fix(spanner): Retry creation of multiplexed session
1 parent 3d585cf commit 34caeb2

3 files changed

Lines changed: 326 additions & 22 deletions

File tree

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

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@
3939
import java.time.Clock;
4040
import java.time.Duration;
4141
import java.time.Instant;
42+
import java.util.Arrays;
4243
import java.util.BitSet;
4344
import java.util.HashMap;
45+
import java.util.List;
4446
import java.util.Map;
4547
import java.util.concurrent.ExecutionException;
4648
import java.util.concurrent.Executors;
@@ -262,6 +264,10 @@ public void close() {
262264
*/
263265
private static final Map<SpannerImpl, BitSet> CHANNEL_USAGE = new HashMap<>();
264266

267+
private static final List<ErrorCode> RETRYABLE_ERROR_CODES =
268+
Arrays.asList(
269+
ErrorCode.DEADLINE_EXCEEDED, ErrorCode.RESOURCE_EXHAUSTED, ErrorCode.UNAVAILABLE);
270+
265271
private final BitSet channelUsage;
266272

267273
private final int numChannels;
@@ -358,11 +364,19 @@ public void close() {
358364
SettableApiFuture.create();
359365
this.readWriteBeginTransactionReferenceFuture = SettableApiFuture.create();
360366
this.multiplexedSessionReference = new AtomicReference<>(initialSessionReferenceFuture);
367+
asyncCreateMultiplexedSession(initialSessionReferenceFuture);
368+
maybeWaitForSessionCreation(
369+
sessionClient.getSpanner().getOptions().getSessionPoolOptions(),
370+
initialSessionReferenceFuture);
371+
}
372+
373+
private void asyncCreateMultiplexedSession(
374+
SettableApiFuture<SessionReference> sessionReferenceFuture) {
361375
this.sessionClient.asyncCreateMultiplexedSession(
362376
new SessionConsumer() {
363377
@Override
364378
public void onSessionReady(SessionImpl session) {
365-
initialSessionReferenceFuture.set(session.getSessionReference());
379+
sessionReferenceFuture.set(session.getSessionReference());
366380
// only start the maintainer if we actually managed to create a session in the first
367381
// place.
368382
maintainer.start();
@@ -395,33 +409,58 @@ public void onSessionCreateFailure(Throwable t, int createFailureForSessionCount
395409
// Mark multiplexes sessions as unimplemented and fall back to regular sessions if
396410
// UNIMPLEMENTED is returned.
397411
maybeMarkUnimplemented(t);
398-
initialSessionReferenceFuture.setException(t);
412+
sessionReferenceFuture.setException(t);
399413
}
400414
});
401-
maybeWaitForSessionCreation(
402-
sessionClient.getSpanner().getOptions().getSessionPoolOptions(),
403-
initialSessionReferenceFuture);
404415
}
405416

406417
void setPool(SessionPool pool) {
407418
this.pool = pool;
408419
}
409420

410-
private static void maybeWaitForSessionCreation(
411-
SessionPoolOptions sessionPoolOptions, ApiFuture<SessionReference> future) {
421+
private void maybeWaitForSessionCreation(
422+
SessionPoolOptions sessionPoolOptions, SettableApiFuture<SessionReference> future) {
412423
Duration waitDuration = sessionPoolOptions.getWaitForMinSessions();
424+
SpannerException lastException = null;
425+
SettableApiFuture<SessionReference> sessionReferenceFuture = future;
426+
413427
if (waitDuration != null && !waitDuration.isZero()) {
414-
long timeoutMillis = waitDuration.toMillis();
415-
try {
416-
future.get(timeoutMillis, TimeUnit.MILLISECONDS);
417-
} catch (ExecutionException executionException) {
418-
throw SpannerExceptionFactory.asSpannerException(executionException.getCause());
419-
} catch (InterruptedException interruptedException) {
420-
throw SpannerExceptionFactory.propagateInterrupt(interruptedException);
421-
} catch (TimeoutException timeoutException) {
422-
throw SpannerExceptionFactory.newSpannerException(
423-
ErrorCode.DEADLINE_EXCEEDED,
424-
"Timed out after waiting " + timeoutMillis + "ms for multiplexed session creation");
428+
Instant endTime = Instant.now().plus(waitDuration);
429+
while (Instant.now().isBefore(endTime)) {
430+
// If any exception is thrown, then retry the multiplexed session creation
431+
if (sessionReferenceFuture == null) {
432+
sessionReferenceFuture = SettableApiFuture.create();
433+
asyncCreateMultiplexedSession(sessionReferenceFuture);
434+
this.multiplexedSessionReference.set(sessionReferenceFuture);
435+
}
436+
// Calculate the remaining time pending for the future to wait for multiplexed session
437+
Duration remainingTime = Duration.between(Instant.now(), endTime);
438+
try {
439+
sessionReferenceFuture.get(remainingTime.toMillis(), TimeUnit.MILLISECONDS);
440+
lastException = null;
441+
} catch (ExecutionException executionException) {
442+
lastException = SpannerExceptionFactory.asSpannerException(executionException.getCause());
443+
} catch (InterruptedException interruptedException) {
444+
lastException = SpannerExceptionFactory.propagateInterrupt(interruptedException);
445+
} catch (TimeoutException timeoutException) {
446+
lastException =
447+
SpannerExceptionFactory.newSpannerException(
448+
ErrorCode.DEADLINE_EXCEEDED,
449+
"Timed out after waiting "
450+
+ waitDuration.toMillis()
451+
+ "ms for multiplexed session creation");
452+
}
453+
// if any exception is thrown, then set the session reference to null to retry the
454+
// multiplexed session creation only if the error code is DEADLINE EXCEEDED, UNAVAILABLE or
455+
// RESOURCE_EXHAUSTED
456+
if (lastException != null && RETRYABLE_ERROR_CODES.contains(lastException.getErrorCode())) {
457+
sessionReferenceFuture = null;
458+
}
459+
}
460+
// if the wait time elapsed and multiplexed session fetch failed then throw the last exception
461+
// that we have received
462+
if (lastException != null) {
463+
throw lastException;
425464
}
426465
}
427466
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -542,16 +542,16 @@ void simulateExecutionTime(
542542
boolean stickyGlobalExceptions,
543543
CountDownLatch freezeLock) {
544544
Uninterruptibles.awaitUninterruptibly(freezeLock);
545-
checkException(globalExceptions, stickyGlobalExceptions);
546-
if (streamIndices.isEmpty()) {
547-
checkException(this.exceptions, stickyException);
548-
}
549545
if (minimumExecutionTime > 0 || randomExecutionTime > 0) {
550546
Uninterruptibles.sleepUninterruptibly(
551547
(randomExecutionTime == 0 ? 0 : RANDOM.nextInt(randomExecutionTime))
552548
+ minimumExecutionTime,
553549
TimeUnit.MILLISECONDS);
554550
}
551+
checkException(globalExceptions, stickyGlobalExceptions);
552+
if (streamIndices.isEmpty()) {
553+
checkException(this.exceptions, stickyException);
554+
}
555555
}
556556

557557
private static void checkException(Queue<Exception> exceptions, boolean keepException) {

0 commit comments

Comments
 (0)