|
18 | 18 |
|
19 | 19 | import static com.google.cloud.spanner.SpannerApiFutures.get;
|
20 | 20 |
|
| 21 | +import com.google.api.core.ApiFunction; |
21 | 22 | import com.google.api.core.ApiFuture;
|
22 | 23 | import com.google.api.core.ApiFutures;
|
23 | 24 | import com.google.cloud.Timestamp;
|
|
42 | 43 | import com.google.cloud.spanner.connection.UnitOfWork.UnitOfWorkState;
|
43 | 44 | import com.google.common.annotations.VisibleForTesting;
|
44 | 45 | import com.google.common.base.Preconditions;
|
| 46 | +import com.google.common.util.concurrent.MoreExecutors; |
45 | 47 | import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
|
46 | 48 | import java.util.ArrayList;
|
47 | 49 | import java.util.Collections;
|
48 | 50 | import java.util.Iterator;
|
49 | 51 | import java.util.LinkedList;
|
50 | 52 | import java.util.List;
|
51 | 53 | import java.util.Stack;
|
| 54 | +import java.util.concurrent.Callable; |
| 55 | +import java.util.concurrent.ExecutionException; |
52 | 56 | import java.util.concurrent.ThreadFactory;
|
53 | 57 | import java.util.concurrent.TimeUnit;
|
| 58 | +import java.util.concurrent.TimeoutException; |
54 | 59 | import org.threeten.bp.Instant;
|
55 | 60 |
|
56 | 61 | /** Implementation for {@link Connection}, the generic Spanner connection API (not JDBC). */
|
@@ -257,28 +262,49 @@ private DdlClient createDdlClient() {
|
257 | 262 |
|
258 | 263 | @Override
|
259 | 264 | 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() { |
260 | 275 | 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()); |
280 | 279 | }
|
| 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()); |
281 | 306 | }
|
| 307 | + return ApiFutures.immediateFuture(null); |
282 | 308 | }
|
283 | 309 |
|
284 | 310 | /** Get the current unit-of-work type of this connection. */
|
|
0 commit comments