Skip to content

Commit 4cb354a

Browse files
olavloitegcf-owl-bot[bot]
authored andcommitted
chore: keep session pool ordering when pinging (googleapis#2695)
* chore: keep session pool ordering when pinging Pinging sessions would move the sessions that were pinged to either the front or the back of the pool (dependingin the session pool configuration), instead of keeping the sessions in the place where they were when being pinged. Bringing a session that is pinged to the front of the pool means that we will prefer using a session that has not really been used for a while, other than for the ping. Keeping the sessions in place is therefore preferable. * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent f9ea807 commit 4cb354a

File tree

2 files changed

+46
-21
lines changed

2 files changed

+46
-21
lines changed

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

+27-11
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import com.google.api.gax.core.ExecutorProvider;
5050
import com.google.api.gax.rpc.ServerStream;
5151
import com.google.cloud.Timestamp;
52+
import com.google.cloud.Tuple;
5253
import com.google.cloud.grpc.GrpcTransportOptions;
5354
import com.google.cloud.grpc.GrpcTransportOptions.ExecutorFactory;
5455
import com.google.cloud.spanner.Options.QueryOption;
@@ -1854,18 +1855,18 @@ private void keepAliveSessions(Instant currTime) {
18541855

18551856
// Keep chugging till there is no session that needs to be kept alive.
18561857
while (numSessionsToKeepAlive > 0) {
1857-
PooledSession sessionToKeepAlive = null;
1858+
Tuple<PooledSession, Integer> sessionToKeepAlive;
18581859
synchronized (lock) {
18591860
sessionToKeepAlive = findSessionToKeepAlive(sessions, keepAliveThreshold, 0);
18601861
}
18611862
if (sessionToKeepAlive == null) {
18621863
break;
18631864
}
18641865
try {
1865-
logger.log(Level.FINE, "Keeping alive session " + sessionToKeepAlive.getName());
1866+
logger.log(Level.FINE, "Keeping alive session " + sessionToKeepAlive.x().getName());
18661867
numSessionsToKeepAlive--;
1867-
sessionToKeepAlive.keepAlive();
1868-
releaseSession(sessionToKeepAlive, false);
1868+
sessionToKeepAlive.x().keepAlive();
1869+
releaseSession(sessionToKeepAlive);
18691870
} catch (SpannerException e) {
18701871
handleException(e, sessionToKeepAlive);
18711872
}
@@ -2314,11 +2315,11 @@ private boolean isClosed() {
23142315
}
23152316
}
23162317

2317-
private void handleException(SpannerException e, PooledSession session) {
2318+
private void handleException(SpannerException e, Tuple<PooledSession, Integer> session) {
23182319
if (isSessionNotFound(e)) {
2319-
invalidateSession(session);
2320+
invalidateSession(session.x());
23202321
} else {
2321-
releaseSession(session, false);
2322+
releaseSession(session);
23222323
}
23232324
}
23242325

@@ -2342,7 +2343,7 @@ private void invalidateSession(PooledSession session) {
23422343
}
23432344
}
23442345

2345-
private PooledSession findSessionToKeepAlive(
2346+
private Tuple<PooledSession, Integer> findSessionToKeepAlive(
23462347
Queue<PooledSession> queue, Instant keepAliveThreshold, int numAlreadyChecked) {
23472348
int numChecked = 0;
23482349
Iterator<PooledSession> iterator = queue.iterator();
@@ -2352,7 +2353,7 @@ private PooledSession findSessionToKeepAlive(
23522353
PooledSession session = iterator.next();
23532354
if (session.delegate.getLastUseTime().isBefore(keepAliveThreshold)) {
23542355
iterator.remove();
2355-
return session;
2356+
return Tuple.of(session, numChecked);
23562357
}
23572358
numChecked++;
23582359
}
@@ -2476,8 +2477,17 @@ private void maybeCreateSession() {
24762477
}
24772478
}
24782479

2479-
/** Releases a session back to the pool. This might cause one of the waiters to be unblocked. */
2480+
private void releaseSession(Tuple<PooledSession, Integer> sessionWithPosition) {
2481+
releaseSession(sessionWithPosition.x(), false, sessionWithPosition.y());
2482+
}
2483+
24802484
private void releaseSession(PooledSession session, boolean isNewSession) {
2485+
releaseSession(session, isNewSession, null);
2486+
}
2487+
2488+
/** Releases a session back to the pool. This might cause one of the waiters to be unblocked. */
2489+
private void releaseSession(
2490+
PooledSession session, boolean isNewSession, @Nullable Integer position) {
24812491
Preconditions.checkNotNull(session);
24822492
synchronized (lock) {
24832493
if (closureFuture != null) {
@@ -2497,7 +2507,12 @@ private void releaseSession(PooledSession session, boolean isNewSession) {
24972507
// more efficient.
24982508
session.releaseToPosition = options.getReleaseToPosition();
24992509
}
2500-
if (session.releaseToPosition == Position.RANDOM && !sessions.isEmpty()) {
2510+
if (position != null) {
2511+
// Make sure we use a valid position, as the number of sessions could have changed in the
2512+
// meantime.
2513+
int actualPosition = Math.min(position, sessions.size());
2514+
sessions.add(actualPosition, session);
2515+
} else if (session.releaseToPosition == Position.RANDOM && !sessions.isEmpty()) {
25012516
// A session should only be added at a random position the first time it is added to
25022517
// the pool or if the pool was deemed unbalanced. All following releases into the pool
25032518
// should normally happen at the default release position (unless the pool is again deemed
@@ -2510,6 +2525,7 @@ private void releaseSession(PooledSession session, boolean isNewSession) {
25102525
} else {
25112526
sessions.addFirst(session);
25122527
}
2528+
session.releaseToPosition = options.getReleaseToPosition();
25132529
} else {
25142530
waiters.poll().put(session);
25152531
}

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

+19-10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.google.cloud.spanner;
1818

1919
import static com.google.common.truth.Truth.assertThat;
20+
import static org.junit.Assert.assertEquals;
2021
import static org.mockito.Mockito.any;
2122
import static org.mockito.Mockito.doAnswer;
2223
import static org.mockito.Mockito.mock;
@@ -175,14 +176,20 @@ public void testKeepAlive() throws Exception {
175176
Session session3 = pool.getSession();
176177
Session session4 = pool.getSession();
177178
Session session5 = pool.getSession();
178-
// Note that session2 was now the first session in the pool as it was the last to receive a
179-
// ping.
180-
assertThat(session3.getName()).isEqualTo(session2.getName());
181-
assertThat(session4.getName()).isEqualTo(session1.getName());
179+
// Pinging a session will put it at the back of the pool. A session that needed a ping to be
180+
// kept alive is not one that should be preferred for use. This means that session2 is the last
181+
// session in the pool, and session1 the second-to-last.
182+
assertEquals(session1.getName(), session3.getName());
183+
assertEquals(session2.getName(), session4.getName());
182184
session5.close();
183185
session4.close();
184186
session3.close();
185187
// Advance the clock to force pings for the sessions in the pool and do three maintenance loops.
188+
// This should ping the sessions in the following order:
189+
// 1. session3 (=session1)
190+
// 2. session4 (=session2)
191+
// The pinged sessions already contains: {session1: 1, session2: 1}
192+
// Note that the pool only pings up to MinSessions sessions.
186193
clock.currentTimeMillis.addAndGet(
187194
TimeUnit.MINUTES.toMillis(options.getKeepAliveIntervalMinutes()) + 1);
188195
runMaintenanceLoop(clock, pool, 3);
@@ -192,16 +199,18 @@ public void testKeepAlive() throws Exception {
192199
// should cause only one session to get a ping.
193200
clock.currentTimeMillis.addAndGet(
194201
TimeUnit.MINUTES.toMillis(options.getKeepAliveIntervalMinutes()) + 1);
195-
// We are now checking out session2 because
202+
// This will be session1, as all sessions were pinged in the previous 3 maintenance loops, and
203+
// this will have brought session1 back to the front of the pool.
196204
Session session6 = pool.getSession();
197205
// The session that was first in the pool now is equal to the initial first session as each full
198206
// round of pings will swap the order of the first MinSessions sessions in the pool.
199207
assertThat(session6.getName()).isEqualTo(session1.getName());
200208
runMaintenanceLoop(clock, pool, 3);
209+
// Running 3 cycles will only ping the 2 sessions in the pool once.
201210
assertThat(pool.totalSessions()).isEqualTo(3);
202211
assertThat(pingedSessions).containsExactly(session1.getName(), 2, session2.getName(), 3);
203212
// Update the last use date and release the session to the pool and do another maintenance
204-
// cycle.
213+
// cycle. This should not ping any sessions.
205214
((PooledSessionFuture) session6).get().markUsed();
206215
session6.close();
207216
runMaintenanceLoop(clock, pool, 3);
@@ -267,10 +276,10 @@ public void testIdleSessions() throws Exception {
267276
Session session3 = pool.getSession().get();
268277
Session session4 = pool.getSession().get();
269278
Session session5 = pool.getSession().get();
270-
// Note that session2 was now the first session in the pool as it was the last to receive a
271-
// ping.
272-
assertThat(session3.getName()).isEqualTo(session2.getName());
273-
assertThat(session4.getName()).isEqualTo(session1.getName());
279+
// Note that pinging sessions does not change the order of the pool. This means that session2
280+
// is still the last session in the pool.
281+
assertThat(session3.getName()).isEqualTo(session1.getName());
282+
assertThat(session4.getName()).isEqualTo(session2.getName());
274283
session5.close();
275284
session4.close();
276285
session3.close();

0 commit comments

Comments
 (0)