This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-dbcp.git
The following commit(s) were added to refs/heads/master by this push: new 0ae96ce Fix StackOverflowError in PoolableConnection.isDisconnectionSqlException #123. 0ae96ce is described below commit 0ae96ced0ffbeada4d08c78373b33f25d40a3ea7 Author: Gary Gregory <gardgreg...@gmail.com> AuthorDate: Sat Aug 14 08:32:01 2021 -0400 Fix StackOverflowError in PoolableConnection.isDisconnectionSqlException #123. This is a cleaned up version of the GitHub PR 123 by newnewcoder. --- src/changes/changes.xml | 3 ++ .../apache/commons/dbcp2/PoolableConnection.java | 47 +++++++++++++++------- .../commons/dbcp2/TestPoolableConnection.java | 19 +++++++++ 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 1210870..49b8e9d 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -65,6 +65,9 @@ The <action> type attribute can be add,update,fix,remove. <action dev="ggregory" type="fix" due-to="Gary Gregory"> Reimplement time tracking in AbandonedTrace with an Instant instead of a long. </action> + <action dev="ggregory" type="fix" due-to="newnewcoder, Gary Gregory"> + Fix StackOverflowError in PoolableConnection.isDisconnectionSqlException #123. + </action> <!-- ADDS --> <action dev="ggregory" type="add" due-to="Gary Gregory"> Add and use AbandonedTrace#setLastUsed(Instant). diff --git a/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java b/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java index 6151429..8145db9 100644 --- a/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java +++ b/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java @@ -212,7 +212,7 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme @Override protected void handleException(final SQLException e) throws SQLException { - fatalSqlExceptionThrown |= isDisconnectionSqlException(e); + fatalSqlExceptionThrown |= isFatalException(e); super.handleException(e); } @@ -241,6 +241,29 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme } /** + * Checks the SQLState of the input exception. + * <p> + * If {@link #disconnectionSqlCodes} has been set, sql states are compared to those in the configured list of fatal + * exception codes. If this property is not set, codes are compared against the default codes in + * {@link Utils#DISCONNECTION_SQL_CODES} and in this case anything starting with #{link + * Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection. + * </p> + * + * @param e SQLException to be examined + * @return true if the exception signals a disconnection + */ + boolean isDisconnectionSqlException(final SQLException e) { + boolean fatalException = false; + final String sqlState = e.getSQLState(); + if (sqlState != null) { + fatalException = disconnectionSqlCodes == null + ? sqlState.startsWith(Utils.DISCONNECTION_SQL_CODE_PREFIX) || Utils.DISCONNECTION_SQL_CODES.contains(sqlState) + : disconnectionSqlCodes.contains(sqlState); + } + return fatalException; + } + + /** * Checks the SQLState of the input exception and any nested SQLExceptions it wraps. * <p> * If {@link #disconnectionSqlCodes} has been set, sql states are compared to those in the @@ -253,19 +276,15 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme * SQLException to be examined * @return true if the exception signals a disconnection */ - private boolean isDisconnectionSqlException(final SQLException e) { - boolean fatalException = false; - final String sqlState = e.getSQLState(); - if (sqlState != null) { - fatalException = disconnectionSqlCodes == null - ? sqlState.startsWith(Utils.DISCONNECTION_SQL_CODE_PREFIX) - || Utils.DISCONNECTION_SQL_CODES.contains(sqlState) - : disconnectionSqlCodes.contains(sqlState); - if (!fatalException) { - final SQLException nextException = e.getNextException(); - if (nextException != null && nextException != e) { - fatalException = isDisconnectionSqlException(e.getNextException()); - } + boolean isFatalException(final SQLException e) { + boolean fatalException = isDisconnectionSqlException(e); + if (!fatalException) { + SQLException parentException = e; + SQLException nextException = e.getNextException(); + while(nextException != null && nextException != parentException && !fatalException) { + fatalException = isDisconnectionSqlException(nextException); + parentException = nextException; + nextException = parentException.getNextException(); } } return fatalException; diff --git a/src/test/java/org/apache/commons/dbcp2/TestPoolableConnection.java b/src/test/java/org/apache/commons/dbcp2/TestPoolableConnection.java index ff7f00c..7c28446 100644 --- a/src/test/java/org/apache/commons/dbcp2/TestPoolableConnection.java +++ b/src/test/java/org/apache/commons/dbcp2/TestPoolableConnection.java @@ -20,10 +20,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.lang.reflect.Method; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; +import java.util.List; import org.apache.commons.pool2.impl.GenericObjectPool; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -187,4 +191,19 @@ public class TestPoolableConnection { assertEquals(0, pool.getNumActive(), "The pool should have no active connections"); } + + @Test + public void testIsDisconnectionSqlExceptionStackOverflow() throws Exception { + final int maxDeep = 100_000; + final SQLException rootException = new SQLException("Data truncated", "22001"); + SQLException parentException = rootException; + for (int i = 0; i <= maxDeep; i++) { + final SQLException childException = new SQLException("Data truncated: " + i, "22001"); + parentException.setNextException(childException); + parentException = childException; + } + final Connection conn = pool.borrowObject(); + assertEquals(false, ((PoolableConnection) conn).isDisconnectionSqlException(rootException)); + assertEquals(false, ((PoolableConnection) conn).isFatalException(rootException)); + } }