diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java index ffaf913580b..e560ca71deb 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java @@ -2048,6 +2048,7 @@ boolean isClosed() { // Does various pool maintenance activities. void maintainPool() { + Instant currTime; synchronized (lock) { if (SessionPool.this.isClosed()) { return; @@ -2059,8 +2060,15 @@ void maintainPool() { / (loopFrequency / 1000L); } this.prevNumSessionsAcquired = SessionPool.this.numSessionsAcquired; + + currTime = clock.instant(); + // Reset the start time for recording the maximum number of sessions in the pool + if (currTime.isAfter(SessionPool.this.lastResetTime.plus(Duration.ofMinutes(10)))) { + SessionPool.this.maxSessionsInUse = SessionPool.this.numSessionsInUse; + SessionPool.this.lastResetTime = currTime; + } } - Instant currTime = clock.instant(); + removeIdleSessions(currTime); // Now go over all the remaining sessions and see if they need to be kept alive explicitly. keepAliveSessions(currTime); @@ -2309,6 +2317,9 @@ enum Position { @GuardedBy("lock") private int maxSessionsInUse = 0; + @GuardedBy("lock") + private Instant lastResetTime = Clock.INSTANCE.instant(); + @GuardedBy("lock") private long numSessionsAcquired = 0; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java index 0389410064a..4249f05c17f 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SessionPoolTest.java @@ -2247,6 +2247,54 @@ public void testWaitOnMinSessionsThrowsExceptionWhenTimeoutIsReached() { pool.maybeWaitOnMinSessions(); } + @Test + public void reset_maxSessionsInUse() { + Clock clock = mock(Clock.class); + when(clock.instant()).thenReturn(Instant.now()); + options = + SessionPoolOptions.newBuilder() + .setMinSessions(1) + .setMaxSessions(3) + .setIncStep(1) + .setMaxIdleSessions(0) + .setPoolMaintainerClock(clock) + .build(); + setupForLongRunningTransactionsCleanup(options); + + pool = createPool(clock); + // Make sure pool has been initialized + pool.getSession().close(); + + // All 3 sessions used. 100% of pool utilised. + PooledSessionFuture readSession1 = pool.getSession(); + PooledSessionFuture readSession2 = pool.getSession(); + PooledSessionFuture readSession3 = pool.getSession(); + + // complete the async tasks + readSession1.get().setEligibleForLongRunning(false); + readSession2.get().setEligibleForLongRunning(false); + readSession3.get().setEligibleForLongRunning(true); + + assertEquals(3, pool.getMaxSessionsInUse()); + assertEquals(3, pool.getNumberOfSessionsInUse()); + + // Release 1 session + readSession1.get().close(); + + // Verify that numSessionsInUse reduces to 2 while maxSessionsInUse remain 3 + assertEquals(3, pool.getMaxSessionsInUse()); + assertEquals(2, pool.getNumberOfSessionsInUse()); + + // ensure that the lastResetTime for maxSessionsInUse > 10 minutes + when(clock.instant()).thenReturn(Instant.now().plus(11, ChronoUnit.MINUTES)); + + pool.poolMaintainer.maintainPool(); + + // Verify that maxSessionsInUse is reset to numSessionsInUse + assertEquals(2, pool.getMaxSessionsInUse()); + assertEquals(2, pool.getNumberOfSessionsInUse()); + } + private void mockKeepAlive(ReadContext context) { ResultSet resultSet = mock(ResultSet.class); when(resultSet.next()).thenReturn(true, false);