This is an automated email from the ASF dual-hosted git repository.

rmaucher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit fb84355f9631d72abccc380952ea27834242030d
Author: opencode <[email protected]>
AuthorDate: Fri Nov 6 10:48:59 2026 +0100

    Publish StatementCache state safely across threads
    
    The pooled connection, pool-wide cache counter and statement type array are 
read from management, close and forced-close threads while the borrow thread 
writes them. Make them volatile and document which configuration fields are 
fixed before chain publication.
---
 .../jdbc/pool/interceptor/StatementCache.java      | 35 ++++++++++------------
 1 file changed, 15 insertions(+), 20 deletions(-)

diff --git 
a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/StatementCache.java
 
b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/StatementCache.java
index b867d828db..4f9dca54cc 100644
--- 
a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/StatementCache.java
+++ 
b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/StatementCache.java
@@ -48,24 +48,16 @@ import org.apache.tomcat.jdbc.pool.util.MessageFactory;
 /**
  * Interceptor that caches {@code PreparedStatement} and/or {@code 
CallableStatement} instances on a connection.
  * <p>
- * A cache hit is answered by this interceptor alone: the rest of the 
interceptor chain is not invoked for it, and a
- * statement recycled into the cache is reset to driver defaults (its query 
timeout goes back to zero). Interceptors
- * listed <em>after</em> this one in {@link 
org.apache.tomcat.jdbc.pool.PoolConfiguration#setJdbcInterceptors(String)
- * jdbcInterceptors} therefore do not see reused statements - relevant to any 
interceptor whose {@code createStatement}
- * hook performs an action rather than only wrapping the statement: {@link 
QueryTimeoutInterceptor} must be listed
- * before this one to keep its timeout on cached statements, while 
wrapping-only interceptors are unaffected: their
- * wrapper is carried inside the cached proxy, and the cache binds a cached 
statement to the physical session the
- * prepare ran on rather than to whatever connection a wrapper beneath it 
reports for the statement. An interceptor
- * that closes tracked statements at the connection return is affected too: 
{@link StatementFinalizer} must be listed
- * before this one, because a parked statement is deliberately left open and a 
tracker beneath the cache would close
- * it out from under a live cache entry; listed after this one, it makes this 
cache refuse to park at all, and
- * statements are closed normally instead of cached. Only {@link 
StatementFinalizer} and its subclasses are recognised
- * by that check; a third-party interceptor with the same behaviour is not 
detected.
+ * A cache hit is answered by this interceptor alone: the rest of the chain is 
not invoked for it, and a recycled
+ * statement is reset to driver defaults. Interceptors listed after this one 
therefore do not see reused statements, so
+ * {@link QueryTimeoutInterceptor} and {@link StatementFinalizer} must be 
listed before the cache. A tracker such as
+ * {@link StatementFinalizer} beneath the cache would close a parked statement 
at connection return, so the cache then
+ * refuses to park at all.
  * <p>
- * With JMX enabled, a cache publishes an MBean below its connection's MBean. 
It is released when the connection is
- * recycled, when the pool closes, or when the pool MBean is deregistered. A 
pool that is abandoned without being
- * closed keeps its registrations - and through them the pool, its sessions 
and its driver class loader - reachable, so
- * a JMX-enabled pool must be closed before it is dropped.
+ * With JMX enabled, a cache publishes an MBean below its connection's MBean. 
It is released when the connection goes
+ * out of service (the chain teardown {@code reset(null, null)}), when the 
pool closes, or when the pool MBean is
+ * deregistered - not on an ordinary return and re-borrow. A pool abandoned 
without closing keeps its registrations,
+ * and through them the pool, its sessions and its driver class loader 
reachable.
  */
 public class StatementCache extends StatementDecoratorInterceptor implements 
StatementCacheMBean {
     private static final Log log = LogFactory.getLog(StatementCache.class);
@@ -112,11 +104,14 @@ public class StatementCache extends 
StatementDecoratorInterceptor implements Sta
     public StatementCache() {
     }
 
+    // Written during chain construction, before the connection publishes the
+    // handler, and never changed afterwards.
     private boolean cachePrepared = true;
     private boolean cacheCallable = false;
     private int maxCacheSize = 50;
-    private PooledConnection pcon;
-    private String[] types;
+    // Re-armed on every borrow and read from management/close threads.
+    private volatile PooledConnection pcon;
+    private volatile String[] types;
 
     private ObjectName oname = null;
 
@@ -194,7 +189,7 @@ public class StatementCache extends 
StatementDecoratorInterceptor implements Sta
     private static final Map<ConnectionPool, AtomicInteger> cacheSizeMap =
             Collections.synchronizedMap(new WeakHashMap<ConnectionPool, 
AtomicInteger>());
 
-    private AtomicInteger cacheSize;
+    private volatile AtomicInteger cacheSize;
 
     /**
      * Whether {@link #disconnected(ConnectionPool, PooledConnection, 
boolean)} is purging this cache. A park that


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to