Skip to content

Commit e7ec96e

Browse files
feat!: add closeAsync() method to Connection (#984)
Co-authored-by: Thiago Nunes <thiagotnunes@google.com>
1 parent 29a200d commit e7ec96e

File tree

3 files changed

+62
-20
lines changed

3 files changed

+62
-20
lines changed

google-cloud-spanner/clirr-ignored-differences.xml

+7
Original file line numberDiff line numberDiff line change
@@ -571,4 +571,11 @@
571571
<className>com/google/cloud/spanner/BackupInfo$Builder</className>
572572
<method>com.google.cloud.spanner.BackupInfo$Builder setEncryptionConfig(com.google.cloud.spanner.encryption.BackupEncryptionConfig)</method>
573573
</difference>
574+
575+
<!-- Connection#closeAsync() -->
576+
<difference>
577+
<differenceType>7012</differenceType>
578+
<className>com/google/cloud/spanner/connection/Connection</className>
579+
<method>com.google.api.core.ApiFuture closeAsync()</method>
580+
</difference>
574581
</differences>

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,19 @@
139139
*/
140140
@InternalApi
141141
public interface Connection extends AutoCloseable {
142-
/** Closes this connection. This is a no-op if the {@link Connection} has alread been closed. */
142+
143+
/** Closes this connection. This is a no-op if the {@link Connection} has already been closed. */
143144
@Override
144145
void close();
145146

147+
/**
148+
* Closes this connection without blocking. This is a no-op if the {@link Connection} has already
149+
* been closed. The {@link Connection} is no longer usable directly after calling this method. The
150+
* returned {@link ApiFuture} is done when the running statement(s) (if any) on the connection
151+
* have finished.
152+
*/
153+
ApiFuture<Void> closeAsync();
154+
146155
/** @return <code>true</code> if this connection has been closed. */
147156
boolean isClosed();
148157

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

+45-19
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static com.google.cloud.spanner.SpannerApiFutures.get;
2020

21+
import com.google.api.core.ApiFunction;
2122
import com.google.api.core.ApiFuture;
2223
import com.google.api.core.ApiFutures;
2324
import com.google.cloud.Timestamp;
@@ -42,15 +43,19 @@
4243
import com.google.cloud.spanner.connection.UnitOfWork.UnitOfWorkState;
4344
import com.google.common.annotations.VisibleForTesting;
4445
import com.google.common.base.Preconditions;
46+
import com.google.common.util.concurrent.MoreExecutors;
4547
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
4648
import java.util.ArrayList;
4749
import java.util.Collections;
4850
import java.util.Iterator;
4951
import java.util.LinkedList;
5052
import java.util.List;
5153
import java.util.Stack;
54+
import java.util.concurrent.Callable;
55+
import java.util.concurrent.ExecutionException;
5256
import java.util.concurrent.ThreadFactory;
5357
import java.util.concurrent.TimeUnit;
58+
import java.util.concurrent.TimeoutException;
5459
import org.threeten.bp.Instant;
5560

5661
/** Implementation for {@link Connection}, the generic Spanner connection API (not JDBC). */
@@ -257,28 +262,49 @@ private DdlClient createDdlClient() {
257262

258263
@Override
259264
public void close() {
265+
try {
266+
closeAsync().get(10L, TimeUnit.SECONDS);
267+
} catch (SpannerException | InterruptedException | ExecutionException | TimeoutException e) {
268+
// ignore and continue to close the connection.
269+
} finally {
270+
statementExecutor.shutdownNow();
271+
}
272+
}
273+
274+
public ApiFuture<Void> closeAsync() {
260275
if (!isClosed()) {
261-
try {
262-
if (isTransactionStarted()) {
263-
try {
264-
rollback();
265-
} catch (Exception e) {
266-
// Ignore as we are closing the connection.
267-
}
268-
}
269-
// Try to wait for the current statement to finish (if any) before we actually close the
270-
// connection.
271-
this.closed = true;
272-
statementExecutor.shutdown();
273-
leakedException = null;
274-
spannerPool.removeConnection(options, this);
275-
statementExecutor.awaitTermination(10L, TimeUnit.SECONDS);
276-
} catch (InterruptedException e) {
277-
// ignore and continue to close the connection.
278-
} finally {
279-
statementExecutor.shutdownNow();
276+
List<ApiFuture<Void>> futures = new ArrayList<>();
277+
if (isTransactionStarted()) {
278+
futures.add(rollbackAsync());
280279
}
280+
// Try to wait for the current statement to finish (if any) before we actually close the
281+
// connection.
282+
this.closed = true;
283+
// Add a no-op statement to the execute. Once this has been executed, we know that all
284+
// preceeding statements have also been executed, as the executor is single-threaded and
285+
// executes all statements in order of submitting.
286+
futures.add(
287+
statementExecutor.submit(
288+
new Callable<Void>() {
289+
@Override
290+
public Void call() throws Exception {
291+
return null;
292+
}
293+
}));
294+
statementExecutor.shutdown();
295+
leakedException = null;
296+
spannerPool.removeConnection(options, this);
297+
return ApiFutures.transform(
298+
ApiFutures.allAsList(futures),
299+
new ApiFunction<List<Void>, Void>() {
300+
@Override
301+
public Void apply(List<Void> input) {
302+
return null;
303+
}
304+
},
305+
MoreExecutors.directExecutor());
281306
}
307+
return ApiFutures.immediateFuture(null);
282308
}
283309

284310
/** Get the current unit-of-work type of this connection. */

0 commit comments

Comments
 (0)