Skip to content

Commit c48f22c

Browse files
authored
fix(4.0.x): sessions were not always removed from checkedOutSessions (#1448)
* fix: sessions were not always removed from checkedOutSessions Sessions were added to a set of checked out sessions when one was checked out from the session pool. When the session was released back to the pool, the session would normally be removed from the set of checked out sessions. The latter would not always happen if the application that checked out the session did not use the session for any reads or writes. chore: fix maven version to 3.8.1 chore: fix maven version for windows * fix: remove double insertion * test: skip slow tests on stable branch
1 parent 207d7f8 commit c48f22c

File tree

6 files changed

+116
-17
lines changed

6 files changed

+116
-17
lines changed

.github/workflows/ci.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ jobs:
1212
java: [7, 8, 11]
1313
steps:
1414
- uses: actions/checkout@v2
15+
- uses: stCarolas/setup-maven@v4
16+
with:
17+
maven-version: 3.8.1
1518
- uses: actions/setup-java@v1
1619
with:
1720
java-version: ${{matrix.java}}
@@ -27,6 +30,9 @@ jobs:
2730
runs-on: windows-latest
2831
steps:
2932
- uses: actions/checkout@v2
33+
- uses: stCarolas/setup-maven@v4
34+
with:
35+
maven-version: 3.8.1
3036
- uses: actions/setup-java@v1
3137
with:
3238
java-version: 8
@@ -41,6 +47,9 @@ jobs:
4147
java: [8, 11]
4248
steps:
4349
- uses: actions/checkout@v2
50+
- uses: stCarolas/setup-maven@v4
51+
with:
52+
maven-version: 3.8.1
4453
- uses: actions/setup-java@v1
4554
with:
4655
java-version: ${{matrix.java}}
@@ -50,6 +59,9 @@ jobs:
5059
runs-on: ubuntu-latest
5160
steps:
5261
- uses: actions/checkout@v2
62+
- uses: stCarolas/setup-maven@v4
63+
with:
64+
maven-version: 3.8.1
5365
- uses: actions/setup-java@v1
5466
with:
5567
java-version: 8
@@ -61,6 +73,9 @@ jobs:
6173
runs-on: ubuntu-latest
6274
steps:
6375
- uses: actions/checkout@v2
76+
- uses: stCarolas/setup-maven@v4
77+
with:
78+
maven-version: 3.8.1
6479
- uses: actions/setup-java@v1
6580
with:
6681
java-version: 8

.github/workflows/integration-tests-against-emulator.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ jobs:
1717

1818
steps:
1919
- uses: actions/checkout@v2
20+
- uses: stCarolas/setup-maven@v4
21+
with:
22+
maven-version: 3.8.1
2023
- uses: actions/setup-java@v1
2124
with:
2225
java-version: 8

.github/workflows/samples.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ jobs:
66
runs-on: ubuntu-latest
77
steps:
88
- uses: actions/checkout@v2
9+
- uses: stCarolas/setup-maven@v4
10+
with:
11+
maven-version: 3.8.1
912
- uses: actions/setup-java@v1
1013
with:
1114
java-version: 8

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

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,25 +1288,27 @@ public void prepareReadWriteTransaction() {
12881288

12891289
@Override
12901290
public void close() {
1291-
synchronized (lock) {
1292-
leakedException = null;
1293-
checkedOutSessions.remove(this);
1294-
}
1295-
PooledSession delegate = getOrNull();
1296-
if (delegate != null) {
1297-
delegate.close();
1291+
try {
1292+
asyncClose().get();
1293+
} catch (InterruptedException e) {
1294+
throw SpannerExceptionFactory.propagateInterrupt(e);
1295+
} catch (ExecutionException e) {
1296+
throw SpannerExceptionFactory.asSpannerException(e.getCause());
12981297
}
12991298
}
13001299

13011300
@Override
13021301
public ApiFuture<Empty> asyncClose() {
1303-
synchronized (lock) {
1304-
leakedException = null;
1305-
checkedOutSessions.remove(this);
1306-
}
1307-
PooledSession delegate = getOrNull();
1308-
if (delegate != null) {
1309-
return delegate.asyncClose();
1302+
try {
1303+
PooledSession delegate = getOrNull();
1304+
if (delegate != null) {
1305+
return delegate.asyncClose();
1306+
}
1307+
} finally {
1308+
synchronized (lock) {
1309+
leakedException = null;
1310+
checkedOutSessions.remove(this);
1311+
}
13101312
}
13111313
return ApiFutures.immediateFuture(Empty.getDefaultInstance());
13121314
}
@@ -1823,7 +1825,8 @@ private static enum Position {
18231825
private final Set<PooledSession> allSessions = new HashSet<>();
18241826

18251827
@GuardedBy("lock")
1826-
private final Set<PooledSessionFuture> checkedOutSessions = new HashSet<>();
1828+
@VisibleForTesting
1829+
final Set<PooledSessionFuture> checkedOutSessions = new HashSet<>();
18271830

18281831
private final SessionConsumer sessionConsumer = new SessionConsumerImpl();
18291832

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

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import java.util.ArrayList;
6161
import java.util.Arrays;
6262
import java.util.List;
63+
import java.util.Set;
6364
import java.util.concurrent.ExecutionException;
6465
import java.util.concurrent.ExecutorService;
6566
import java.util.concurrent.Executors;
@@ -211,13 +212,18 @@ public void testWriteAtLeastOnceWithCommitStats() {
211212

212213
@Test
213214
public void singleUse() {
214-
DatabaseClient client =
215-
spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
215+
DatabaseClientImpl client =
216+
(DatabaseClientImpl)
217+
spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
218+
Set<PooledSessionFuture> checkedOut = client.pool.checkedOutSessions;
219+
assertThat(checkedOut).isEmpty();
216220
try (ResultSet rs = client.singleUse().executeQuery(SELECT1)) {
217221
assertThat(rs.next()).isTrue();
222+
assertThat(checkedOut).hasSize(1);
218223
assertThat(rs.getLong(0)).isEqualTo(1L);
219224
assertThat(rs.next()).isFalse();
220225
}
226+
assertThat(checkedOut).isEmpty();
221227
}
222228

223229
@Test
@@ -1642,4 +1648,71 @@ public void testTransactionManagerAsync_usesOptions() {
16421648

16431649
verify(session).transactionManagerAsync(option);
16441650
}
1651+
1652+
@Test
1653+
public void singleUseNoAction_ClearsCheckedOutSession() {
1654+
DatabaseClientImpl client =
1655+
(DatabaseClientImpl)
1656+
spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
1657+
Set<PooledSessionFuture> checkedOut = client.pool.checkedOutSessions;
1658+
assertThat(checkedOut).isEmpty();
1659+
1660+
// Getting a single use read-only transaction and not using it should not cause any sessions
1661+
// to be stuck in the map of checked out sessions.
1662+
client.singleUse().close();
1663+
1664+
assertThat(checkedOut).isEmpty();
1665+
}
1666+
1667+
@Test
1668+
public void singleUseReadOnlyTransactionNoAction_ClearsCheckedOutSession() {
1669+
DatabaseClientImpl client =
1670+
(DatabaseClientImpl)
1671+
spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
1672+
Set<PooledSessionFuture> checkedOut = client.pool.checkedOutSessions;
1673+
assertThat(checkedOut).isEmpty();
1674+
1675+
client.singleUseReadOnlyTransaction().close();
1676+
1677+
assertThat(checkedOut).isEmpty();
1678+
}
1679+
1680+
@Test
1681+
public void readWriteTransactionNoAction_ClearsCheckedOutSession() {
1682+
DatabaseClientImpl client =
1683+
(DatabaseClientImpl)
1684+
spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
1685+
Set<PooledSessionFuture> checkedOut = client.pool.checkedOutSessions;
1686+
assertThat(checkedOut).isEmpty();
1687+
1688+
client.readWriteTransaction();
1689+
1690+
assertThat(checkedOut).isEmpty();
1691+
}
1692+
1693+
@Test
1694+
public void readOnlyTransactionNoAction_ClearsCheckedOutSession() {
1695+
DatabaseClientImpl client =
1696+
(DatabaseClientImpl)
1697+
spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
1698+
Set<PooledSessionFuture> checkedOut = client.pool.checkedOutSessions;
1699+
assertThat(checkedOut).isEmpty();
1700+
1701+
client.readOnlyTransaction().close();
1702+
1703+
assertThat(checkedOut).isEmpty();
1704+
}
1705+
1706+
@Test
1707+
public void transactionManagerNoAction_ClearsCheckedOutSession() {
1708+
DatabaseClientImpl client =
1709+
(DatabaseClientImpl)
1710+
spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
1711+
Set<PooledSessionFuture> checkedOut = client.pool.checkedOutSessions;
1712+
assertThat(checkedOut).isEmpty();
1713+
1714+
client.transactionManager().close();
1715+
1716+
assertThat(checkedOut).isEmpty();
1717+
}
16451718
}

google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITBackupTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import org.junit.Before;
7070
import org.junit.BeforeClass;
7171
import org.junit.ClassRule;
72+
import org.junit.Ignore;
7273
import org.junit.Test;
7374
import org.junit.experimental.categories.Category;
7475
import org.junit.runner.RunWith;
@@ -190,6 +191,7 @@ private void waitForDbOperations(String backupId) throws InterruptedException {
190191
}
191192

192193
@Test
194+
@Ignore("Do not run slow test in stable branch")
193195
public void testBackups() throws InterruptedException, ExecutionException {
194196
// Create two test databases in parallel.
195197
String db1Id = testHelper.getUniqueDatabaseId() + "_db1";

0 commit comments

Comments
 (0)