Skip to content

Commit 091bd1d

Browse files
authored
fix: gracefully ignore RejectedExecutionException during Connection#close() (#1887)
1 parent 9247203 commit 091bd1d

File tree

1 file changed

+29
-20
lines changed

1 file changed

+29
-20
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import java.util.List;
5858
import java.util.Stack;
5959
import java.util.concurrent.ExecutionException;
60+
import java.util.concurrent.RejectedExecutionException;
6061
import java.util.concurrent.ThreadFactory;
6162
import java.util.concurrent.TimeUnit;
6263
import java.util.concurrent.TimeoutException;
@@ -292,30 +293,38 @@ public void close() {
292293
}
293294

294295
public ApiFuture<Void> closeAsync() {
295-
if (!isClosed()) {
296-
List<ApiFuture<Void>> futures = new ArrayList<>();
297-
if (isBatchActive()) {
298-
abortBatch();
299-
}
300-
if (isTransactionStarted()) {
296+
synchronized (this) {
297+
if (!isClosed()) {
298+
List<ApiFuture<Void>> futures = new ArrayList<>();
299+
if (isBatchActive()) {
300+
abortBatch();
301+
}
302+
if (isTransactionStarted()) {
303+
try {
304+
futures.add(rollbackAsync());
305+
} catch (Exception exception) {
306+
// ignore and continue to close the connection.
307+
}
308+
}
309+
// Try to wait for the current statement to finish (if any) before we actually close the
310+
// connection.
311+
this.closed = true;
312+
// Add a no-op statement to the executor. Once this has been executed, we know that all
313+
// preceding statements have also been executed, as the executor is single-threaded and
314+
// executes all statements in order of submitting. The Executor#submit method can throw a
315+
// RejectedExecutionException if the executor is no longer in state where it accepts new
316+
// tasks.
301317
try {
302-
futures.add(rollbackAsync());
303-
} catch (Exception exception) {
318+
futures.add(statementExecutor.submit(() -> null));
319+
} catch (RejectedExecutionException ignored) {
304320
// ignore and continue to close the connection.
305321
}
322+
statementExecutor.shutdown();
323+
leakedException = null;
324+
spannerPool.removeConnection(options, this);
325+
return ApiFutures.transform(
326+
ApiFutures.allAsList(futures), ignored -> null, MoreExecutors.directExecutor());
306327
}
307-
// Try to wait for the current statement to finish (if any) before we actually close the
308-
// connection.
309-
this.closed = true;
310-
// Add a no-op statement to the executor. Once this has been executed, we know that all
311-
// preceding statements have also been executed, as the executor is single-threaded and
312-
// executes all statements in order of submitting.
313-
futures.add(statementExecutor.submit(() -> null));
314-
statementExecutor.shutdown();
315-
leakedException = null;
316-
spannerPool.removeConnection(options, this);
317-
return ApiFutures.transform(
318-
ApiFutures.allAsList(futures), ignored -> null, MoreExecutors.directExecutor());
319328
}
320329
return ApiFutures.immediateFuture(null);
321330
}

0 commit comments

Comments
 (0)