|
57 | 57 | import java.util.List; |
58 | 58 | import java.util.Stack; |
59 | 59 | import java.util.concurrent.ExecutionException; |
| 60 | +import java.util.concurrent.RejectedExecutionException; |
60 | 61 | import java.util.concurrent.ThreadFactory; |
61 | 62 | import java.util.concurrent.TimeUnit; |
62 | 63 | import java.util.concurrent.TimeoutException; |
@@ -292,30 +293,38 @@ public void close() { |
292 | 293 | } |
293 | 294 |
|
294 | 295 | 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. |
301 | 317 | try { |
302 | | - futures.add(rollbackAsync()); |
303 | | - } catch (Exception exception) { |
| 318 | + futures.add(statementExecutor.submit(() -> null)); |
| 319 | + } catch (RejectedExecutionException ignored) { |
304 | 320 | // ignore and continue to close the connection. |
305 | 321 | } |
| 322 | + statementExecutor.shutdown(); |
| 323 | + leakedException = null; |
| 324 | + spannerPool.removeConnection(options, this); |
| 325 | + return ApiFutures.transform( |
| 326 | + ApiFutures.allAsList(futures), ignored -> null, MoreExecutors.directExecutor()); |
306 | 327 | } |
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()); |
319 | 328 | } |
320 | 329 | return ApiFutures.immediateFuture(null); |
321 | 330 | } |
|
0 commit comments