Skip to content

Commit ee181ae

Browse files
committed
fix: only close and return sessions once
Closing a pooled session multiple times would cause it to be added to the pool multiple times. This fix prevents this by keeping track of the state of a session that has been checked out.
1 parent f680c9e commit ee181ae

File tree

2 files changed

+49
-3
lines changed

2 files changed

+49
-3
lines changed

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,6 +1314,7 @@ default void addListener(Runnable listener, Executor exec) {}
13141314
class PooledSessionFuture extends SimpleForwardingListenableFuture<PooledSession>
13151315
implements SessionFuture {
13161316

1317+
private boolean closed;
13171318
private volatile LeakedSessionException leakedException;
13181319
private final AtomicBoolean inUse = new AtomicBoolean();
13191320
private final CountDownLatch initialized = new CountDownLatch(1);
@@ -1331,6 +1332,7 @@ void clearLeakedException() {
13311332
}
13321333

13331334
private void markCheckedOut() {
1335+
13341336
if (options.isTrackStackTraceOfSessionCheckout()) {
13351337
this.leakedException = new LeakedSessionException();
13361338
synchronized (SessionPool.this.lock) {
@@ -1520,6 +1522,13 @@ public void close() {
15201522

15211523
@Override
15221524
public ApiFuture<Empty> asyncClose() {
1525+
synchronized (this) {
1526+
// Don't add the session twice to the pool if a resource is being closed multiple times.
1527+
if (closed) {
1528+
return ApiFutures.immediateFuture(Empty.getDefaultInstance());
1529+
}
1530+
closed = true;
1531+
}
15231532
try {
15241533
PooledSession delegate = getOrNull();
15251534
if (delegate != null) {
@@ -2709,9 +2718,7 @@ private Tuple<PooledSession, Integer> findSessionToKeepAlive(
27092718
return null;
27102719
}
27112720

2712-
/**
2713-
* @return true if this {@link SessionPool} is still valid.
2714-
*/
2721+
/** @return true if this {@link SessionPool} is still valid. */
27152722
boolean isValid() {
27162723
synchronized (lock) {
27172724
return closureFuture == null && resourceNotFoundException == null;
@@ -3142,6 +3149,13 @@ int totalSessions() {
31423149
}
31433150
}
31443151

3152+
@VisibleForTesting
3153+
int numSessionsInPool() {
3154+
synchronized (lock) {
3155+
return sessions.size();
3156+
}
3157+
}
3158+
31453159
private ApiFuture<Empty> closeSessionAsync(final PooledSession sess) {
31463160
ApiFuture<Empty> res = sess.delegate.asyncClose();
31473161
res.addListener(

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,6 +1237,38 @@ public void testAbandonedAsyncTransactionManager_rollbackFails() throws Exceptio
12371237
assertTrue(gotException);
12381238
}
12391239

1240+
@Test
1241+
public void testRollbackAndCloseEmptyTransaction() throws Exception {
1242+
DatabaseClientImpl client = (DatabaseClientImpl) clientWithEmptySessionPool();
1243+
1244+
// Create a transaction manager and start a transaction. This should create a session and
1245+
// check it out of the pool.
1246+
AsyncTransactionManager manager = client.transactionManagerAsync();
1247+
manager.beginAsync().get();
1248+
assertEquals(0, client.pool.numSessionsInPool());
1249+
assertEquals(1, client.pool.totalSessions());
1250+
1251+
// Rolling back an empty transaction will return the session to the pool.
1252+
manager.rollbackAsync().get();
1253+
assertEquals(1, client.pool.numSessionsInPool());
1254+
// Closing the transaction manager should not cause the session to be added to the pool again.
1255+
manager.close();
1256+
// The total number of sessions does not change.
1257+
assertEquals(1, client.pool.numSessionsInPool());
1258+
1259+
// Check out 2 sessions. Make sure that the pool really created a new session, and did not
1260+
// return the same session twice.
1261+
AsyncTransactionManager manager1 = client.transactionManagerAsync();
1262+
AsyncTransactionManager manager2 = client.transactionManagerAsync();
1263+
manager1.beginAsync().get();
1264+
manager2.beginAsync().get();
1265+
assertEquals(2, client.pool.totalSessions());
1266+
assertEquals(0, client.pool.numSessionsInPool());
1267+
manager1.close();
1268+
manager2.close();
1269+
assertEquals(2, client.pool.numSessionsInPool());
1270+
}
1271+
12401272
private boolean isMultiplexedSessionsEnabled() {
12411273
if (spanner.getOptions() == null || spanner.getOptions().getSessionPoolOptions() == null) {
12421274
return false;

0 commit comments

Comments
 (0)