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 c81b1a134a240f7acfc6cc208c4cde3e6e918315 Author: opencode <[email protected]> AuthorDate: Thu Nov 5 14:44:58 2026 +0100 Treat a non-fatal Error as a failed transaction termination PooledConnection.silentlyCommitTransactionIfNeeded and silentlyRollbackTransactionIfNeeded caught only SQLException and RuntimeException. validate() calls the rollback helper from a finally block outside its main Throwable guard, so a LinkageError from getAutoCommit(), commit() or rollback() escaped and stranded the claimed connection between the idle and busy queues; the commit helper had the same gap. Catch Throwable in both helpers, apply handleThrowable, and keep the existing failed validation result and logging. Apply the same treatment to the defensive statement close in validate's catch block. Add an Error mode to the failing-driver test and cover a rollback Error on the borrow path. --- .../apache/tomcat/jdbc/pool/PooledConnection.java | 30 ++++++++++++++++++---- .../jdbc/pool/TestThrowingBuiltInValidation.java | 15 ++++++++++- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PooledConnection.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PooledConnection.java index d8a4813887..53098a27a3 100644 --- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PooledConnection.java +++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PooledConnection.java @@ -269,7 +269,11 @@ public class PooledConnection implements PooledConnectionMBean { if (connection != null) { try { this.disconnect(false); - } catch (Exception x) { + } catch (Throwable x) { + // A non-fatal Error from the previous session's teardown is + // a failed disconnect, not a reason to abort the new + // connection attempt. + ExceptionUtils.handleThrowable(x); if (log.isDebugEnabled()) { log.debug("Unable to disconnect previous connection.", x); } @@ -1169,8 +1173,9 @@ public class PooledConnection implements PooledConnectionMBean { if (stmt != null) { try { stmt.close(); - } catch (Exception ignore2) { - /* NOOP */} + } catch (Throwable ignore2) { + ExceptionUtils.handleThrowable(ignore2); + } } } finally { @@ -1201,7 +1206,11 @@ public class PooledConnection implements PooledConnectionMBean { return true; } catch (SQLException e) { log.debug("Failed to commit transaction", e); - } catch (RuntimeException e) { + } catch (Throwable e) { + // A non-fatal Error is the same failed commit an unchecked + // exception is: it must not escape through the caller's finally + // block and strand the connection. + ExceptionUtils.handleThrowable(e); if (getPoolProperties().getLogValidationErrors()) { log.error(MessageFactory.get("pooledConnection.commitFailed"), e); } else { @@ -1225,7 +1234,10 @@ public class PooledConnection implements PooledConnectionMBean { return true; } catch (SQLException e) { log.debug("Failed to rollback transaction", e); - } catch (RuntimeException e) { + } catch (Throwable e) { + // A non-fatal Error is handled exactly like the unchecked + // failure below. + ExceptionUtils.handleThrowable(e); // A failure to end the transaction is a failure whether the // driver reports it checked or unchecked: the caller reads the // false as a failed validation and takes the session out of @@ -1615,6 +1627,14 @@ public class PooledConnection implements PooledConnectionMBean { } } + /** + * Returns a diagnostic rendering of this pooled connection: the driver's own {@link Connection#toString()} when a + * physical session is present, or {@code null}. The driver's text is not masked and may include the URL, the user + * or other session details, so pool warnings that pass a pooled connection should treat it as best-effort + * diagnostic output rather than as a credential-safe view. + * + * @return the diagnostic rendering + */ @Override public String toString() { return "PooledConnection[" + (connection != null ? connection.toString() : "null") + "]"; diff --git a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestThrowingBuiltInValidation.java b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestThrowingBuiltInValidation.java index 734da7cbe3..96fa1eaa1e 100644 --- a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestThrowingBuiltInValidation.java +++ b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestThrowingBuiltInValidation.java @@ -164,6 +164,14 @@ class TestThrowingBuiltInValidation extends JdbcPoolBaseTest { assertFailingBuiltInCheckOnBorrowDoesNotStrand("rollback", true); } + @Test + void errorRollbackFailureOnBorrowDoesNotStrandTheSlot() throws Exception { + // The transaction helper is called from validate()'s finally block, + // outside the main guard; a non-fatal Error from rollback() must be + // read as the failed termination it is. + assertFailingBuiltInCheckOnBorrowDoesNotStrand("rollback=error", true); + } + @Test void uncheckedRollbackFailureAfterValidationQueryDoesNotEscapeTheFinally() throws Exception { @@ -430,11 +438,16 @@ class TestThrowingBuiltInValidation extends JdbcPoolBaseTest { String armed = FAIL_FROM.get(); if (armed != null) { boolean honestFalse = armed.endsWith("=false"); - String name = honestFalse ? armed.substring(0, armed.length() - "=false".length()) : armed; + boolean error = armed.endsWith("=error"); + String suffix = honestFalse ? "=false" : error ? "=error" : ""; + String name = suffix.isEmpty() ? armed : armed.substring(0, armed.length() - suffix.length()); if (method.getName().equals(name)) { if (honestFalse) { return Boolean.FALSE; } + if (error) { + throw new NoClassDefFoundError("boom from " + name + "()"); + } throw new IllegalStateException("boom from " + name + "()"); } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
