This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 11.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 92e7718a86016f029762594d46beaf0e68eb9b80 Author: opencode <[email protected]> AuthorDate: Sun Oct 25 22:22:23 2026 +0100 Run the idle eviction sweep whenever the pool is over min-idle The cleaner guarded checkIdle() on the idle count while the eviction predicate releases based on the total pool size, so a busy pool with idle connections at or below the min-idle floor never got swept and aged connections lingered past minEvictableIdleTimeMillis. Align the guard with the predicate. Also pin in TestUnconnectedPlaceholder that the placeholder can only be removed by the idle validation, by switching age-based eviction off for that pool. --- .../apache/tomcat/jdbc/pool/ConnectionPool.java | 8 +++++-- .../apache/tomcat/jdbc/pool/TestPoolEviction.java | 28 ++++++++++++++++++++++ .../jdbc/pool/TestUnconnectedPlaceholder.java | 7 +++--- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java index 5f41e01e03..53b45567e4 100644 --- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java +++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java @@ -1765,8 +1765,12 @@ public class ConnectionPool { || pool.getPoolProperties().getSuspectTimeout() > 0) { pool.checkAbandoned(); } - if (pool.getPoolProperties().getMinIdle() < pool.idle - .size()) { + // the eviction predicate in checkIdle() compares + // against the total pool size, not the idle count: a + // busy pool can be over the min-idle floor while idle + // stays at or below it + if (pool.getPoolProperties().getMinIdle() + < pool.getSize()) { pool.checkIdle(); } if (pool.getPoolProperties().isTestWhileIdle()) { diff --git a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestPoolEviction.java b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestPoolEviction.java index 4ffb02000f..39e5f27eb3 100644 --- a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestPoolEviction.java +++ b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestPoolEviction.java @@ -145,6 +145,34 @@ class TestPoolEviction extends JdbcPoolBaseTest { } } + @Test + void agedIdleConnectionIsEvictedWhileBusyKeepsSizeAboveMinIdle() + throws Exception { + DataSource ds = newDataSource(); + ds.getPoolProperties().setMinIdle(1); + ds.getPoolProperties().setTimeBetweenEvictionRunsMillis(200); + ds.getPoolProperties().setMinEvictableIdleTimeMillis(100); + ds.createPool(); + + // one busy connection above the min-idle floor: the pool is over the + // floor by total size, yet idle never exceeds the floor, so the + // cleaner's guard must not skip the eviction sweep + Connection hold = ds.getConnection(); + try { + Connection extra = ds.getConnection(); + assertEquals(2, poolSessions()); + extra.close(); // aged after 100 ms; evictable while size(2) > minIdle(1) + + awaitSessions(1); // only the busy connection's session may survive + } finally { + // release the busy slot even if the eviction assertion above + // failed, so a red run cannot pollute the shared server; the + // returned connection then sits at the min-idle floor, where it + // must stay and where the teardown closes it + hold.close(); + } + } + @Test void borrowAfterEvictionCycleYieldsFreshConnection() throws Exception { DataSource ds = newDataSource(); diff --git a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestUnconnectedPlaceholder.java b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestUnconnectedPlaceholder.java index ecfbe650f5..72437801fd 100644 --- a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestUnconnectedPlaceholder.java +++ b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestUnconnectedPlaceholder.java @@ -77,9 +77,10 @@ class TestUnconnectedPlaceholder extends JdbcPoolBaseTest { props.setPassword(""); props.setJmxEnabled(false); props.setInitialSize(0); - // one above the placeholder count so the age-based eviction guard - // (minIdle < idle) never removes it: only the idle validation can - props.setMinIdle(1); + // age-based eviction is switched off, so the placeholder can only + // ever be removed by the idle validation this test is about + props.setMinIdle(0); + props.setMinEvictableIdleTimeMillis(-1); props.setMaxActive(1); props.setMaxIdle(1); // <= 0 means: wait forever for a notification --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
