fix: only close and return sessions once · googleapis/java-spanner@a2fc47b · GitHub | Latest TMZ Celebrity News & Gossip | Watch TMZ Live
Skip to content

Commit a2fc47b

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 a2fc47b

File tree

2 files changed

+54
-3
lines changed

2 files changed

+54
-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: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,6 +1237,43 @@ public void testAbandonedAsyncTransactionManager_rollbackFails() throws Exceptio
12371237
assertTrue(gotException);
12381238
}
12391239

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

0 commit comments

Comments
 (0)

TMZ Celebrity News – Breaking Stories, Videos & Gossip

Looking for the latest TMZ celebrity news? You've come to the right place. From shocking Hollywood scandals to exclusive videos, TMZ delivers it all in real time.

Whether it’s a red carpet slip-up, a viral paparazzi moment, or a legal drama involving your favorite stars, TMZ news is always first to break the story. Stay in the loop with daily updates, insider tips, and jaw-dropping photos.

🎥 Watch TMZ Live

TMZ Live brings you daily celebrity news and interviews straight from the TMZ newsroom. Don’t miss a beat—watch now and see what’s trending in Hollywood.