Skip to content

Commit 32b2373

Browse files
fix: only close and return sessions once (#3846)
* 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. * chore: generate libraries at Wed Apr 30 16:40:04 UTC 2025 --------- Co-authored-by: cloud-java-bot <[email protected]>
1 parent 85a0820 commit 32b2373

File tree

2 files changed

+54
-0
lines changed

2 files changed

+54
-0
lines changed

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

+16
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) {
@@ -3142,6 +3151,13 @@ int totalSessions() {
31423151
}
31433152
}
31443153

3154+
@VisibleForTesting
3155+
int numSessionsInPool() {
3156+
synchronized (lock) {
3157+
return sessions.size();
3158+
}
3159+
}
3160+
31453161
private ApiFuture<Empty> closeSessionAsync(final PooledSession sess) {
31463162
ApiFuture<Empty> res = sess.delegate.asyncClose();
31473163
res.addListener(

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

+38
Original file line numberDiff line numberDiff line change
@@ -1237,6 +1237,44 @@ public void testAbandonedAsyncTransactionManager_rollbackFails() throws Exceptio
12371237
assertTrue(gotException);
12381238
}
12391239

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

0 commit comments

Comments
 (0)