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

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

commit 9280097e62b619f92d9660fcaf31d53eed67c988
Author: opencode <[email protected]>
AuthorDate: Sun Oct 25 23:00:52 2026 +0100

    Rethrow message-less borrow failures from getConnectionAsync
    
    The empty-pool detection in the asynchronous borrow dereferenced the
    message of the caught SQLException without a null check, so a borrow
    failure that carried no message - which the pool itself manufactures
    when wrapping a non-SQL connector failure - surfaced to the caller as
    a NullPointerException instead of the original SQLException. A missing
    message is now treated as a missing "NoWait" marker, so the failure is
    rethrown intact.
---
 .../apache/tomcat/jdbc/pool/ConnectionPool.java    |  7 ++-
 .../apache/tomcat/jdbc/pool/TestAsyncBorrow.java   | 67 ++++++++++++++++++++++
 2 files changed, 73 insertions(+), 1 deletion(-)

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 4565997419..5c2788819a 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
@@ -179,7 +179,12 @@ public class ConnectionPool {
                 return new ConnectionFuture(pc);
             }
         }catch (SQLException x) {
-            if (x.getMessage().indexOf("NoWait")<0) {
+            // JDBC exceptions may carry no message at all; a missing
+            // "NoWait" marker must be assumed for those, so that the
+            // original failure reaches the caller instead of a
+            // NullPointerException from dereferencing the message
+            String msg = x.getMessage();
+            if (msg == null || msg.indexOf("NoWait")<0) {
                 throw x;
             }
         }
diff --git 
a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestAsyncBorrow.java
 
b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestAsyncBorrow.java
index dcb43154ad..a42db57d1d 100644
--- 
a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestAsyncBorrow.java
+++ 
b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestAsyncBorrow.java
@@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import org.junit.jupiter.api.Test;
@@ -118,6 +119,72 @@ class TestAsyncBorrow extends JdbcPoolBaseTest {
         }
     }
 
+    @Test
+    void asyncBorrowFailureWithoutAMessageStillSurfacesAsSqlException()
+            throws Exception {
+        // A failing connector whose exception carries no message: the pool
+        // wraps it as a message-less SQLException, and the async borrow must
+        // rethrow that failure rather than dereference its missing message
+        DataSource ds = newDataSource();
+        ds.getPoolProperties().setInitialSize(0);
+        ds.setDataSource(new javax.sql.DataSource() {
+            @Override
+            public Connection getConnection() {
+                throw new IllegalStateException();
+            }
+
+            @Override
+            public Connection getConnection(String user, String password) {
+                throw new IllegalStateException();
+            }
+
+            @Override
+            public java.io.PrintWriter getLogWriter() {
+                return null;
+            }
+
+            @Override
+            public void setLogWriter(java.io.PrintWriter out) {
+                // no logging
+            }
+
+            @Override
+            public void setLoginTimeout(int seconds) {
+                // no timeout
+            }
+
+            @Override
+            public int getLoginTimeout() {
+                return 0;
+            }
+
+            @Override
+            public java.util.logging.Logger getParentLogger()
+                    throws java.sql.SQLFeatureNotSupportedException {
+                throw new java.sql.SQLFeatureNotSupportedException();
+            }
+
+            @Override
+            public <T> T unwrap(Class<T> iface) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public boolean isWrapperFor(Class<?> iface) {
+                return false;
+            }
+        });
+
+        SQLException failure = assertThrows(SQLException.class,
+                ds::getConnectionAsync,
+                "the borrow failure must reach the caller as a SQLException "
+                        + "even when its message is null");
+        assertNull(failure.getMessage(),
+                "the rethrown failure must keep its absent message");
+        assertTrue(failure.getCause() instanceof IllegalStateException,
+                "the wrapped connector failure must be preserved as cause");
+    }
+
     @Test
     void concurrentAsyncBorrowersAreAllServed() throws Exception {
         final int borrowers = 6;


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

Reply via email to