Author: psteitz Date: Wed Jan 28 13:33:18 2015 New Revision: 1655301 URL: http://svn.apache.org/r1655301 Log: Added fastFailValidation property to PC, BDS. JIRA: DBCP-427.
Modified: commons/proper/dbcp/trunk/src/changes/changes.xml commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceMXBean.java commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/Utils.java commons/proper/dbcp/trunk/src/main/resources/org/apache/commons/dbcp2/LocalStrings.properties commons/proper/dbcp/trunk/src/site/xdoc/configuration.xml commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestBasicDataSourceFactory.java commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestPoolableConnection.java commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TesterConnection.java Modified: commons/proper/dbcp/trunk/src/changes/changes.xml URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/changes/changes.xml?rev=1655301&r1=1655300&r2=1655301&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/changes/changes.xml (original) +++ commons/proper/dbcp/trunk/src/changes/changes.xml Wed Jan 28 13:33:18 2015 @@ -94,6 +94,11 @@ The <action> type attribute can be add,u <action issue="DBCP-423" dev="psteitz" type="update"> Made Datasources implement AutoCloseable. </action> + <action issue="DBCP-427" dev="psteitz" type="add" due-to="Vladimir Konkov"> + Added fastFailValidation property to PooloableConnection, configurable in + BasicDataSource. When set to true, connections that have previously thrown + fatal disconnection errors will fail validation immediately (no driver calls). + </action> </release> <release version="2.0.1" date="24 May 2014" description="This is a bug fix release."> <action dev="markt" type="fix"> Modified: commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java?rev=1655301&r1=1655300&r2=1655301&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java (original) +++ commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java Wed Jan 28 13:33:18 2015 @@ -31,8 +31,10 @@ import java.sql.SQLFeatureNotSupportedEx import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Properties; +import java.util.Set; import java.util.logging.Logger; import javax.management.InstanceAlreadyExistsException; @@ -1267,13 +1269,15 @@ public class BasicDataSource implements public long getMaxConnLifetimeMillis() { return maxConnLifetimeMillis; } - + private boolean logExpiredConnections = true; - + /** * When {@link #getMaxConnLifetimeMillis()} is set to limit connection lifetime, * this property determines whether or not log messages are generated when the - * pool closes connections due to maximum lifetime exceeded. + * pool closes connections due to maximum lifetime exceeded. + * + * @since 2.1 */ @Override public boolean isLogExpiredConnections() { @@ -1292,7 +1296,7 @@ public class BasicDataSource implements public void setMaxConnLifetimeMillis(long maxConnLifetimeMillis) { this.maxConnLifetimeMillis = maxConnLifetimeMillis; } - + /** * When {@link #getMaxConnLifetimeMillis()} is set to limit connection lifetime, * this property determines whether or not log messages are generated when the @@ -1349,7 +1353,6 @@ public class BasicDataSource implements this.enableAutoCommitOnReturn = enableAutoCommitOnReturn; } - private boolean rollbackOnReturn = true; /** @@ -1370,6 +1373,97 @@ public class BasicDataSource implements this.rollbackOnReturn = rollbackOnReturn; } + private volatile Set<String> disconnectionSqlCodes; + + /** + * Returns the set of SQL_STATE codes considered to signal fatal conditions. + * @return fatal disconnection state codes + * @see #setDisconnectionSqlCodes(Collection) + * @since 2.1 + */ + public Set<String> getDisconnectionSqlCodes() { + Set<String> result = disconnectionSqlCodes; + if (result == null) { + return Collections.emptySet(); + } + return result; + } + + /** + * Provides the same data as {@link #getDisconnectionSqlCodes} but in an + * array so it is accessible via JMX. + * @since 2.1 + */ + @Override + public String[] getDisconnectionSqlCodesAsArray() { + Collection<String> result = getDisconnectionSqlCodes(); + return result.toArray(new String[result.size()]); + } + + /** + * Sets the SQL_STATE codes considered to signal fatal conditions. + * <p> + * Overrides the defaults in {@link Utils#DISCONNECTION_SQL_CODES} + * (plus anything starting with {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). + * If this property is non-null and {@link #isFastFailValidation()} is + * {@code true}, whenever connections created by this datasource generate exceptions + * with SQL_STATE codes in this list, they will be marked as "fatally disconnected" + * and subsequent validations will fail fast (no attempt at isValid or validation + * query).</p> + * <p> + * If {@link #isFastFailValidation()} is {@code false} setting this property has no + * effect.</p> + * <p> + * Note: this method currently has no effect once the pool has been + * initialized. The pool is initialized the first time one of the + * following methods is invoked: {@code getConnection, setLogwriter, + * setLoginTimeout, getLoginTimeout, getLogWriter}.</p> + * + * @param disconnectionSqlCodes SQL_STATE codes considered to signal fatal conditions + * @since 2.1 + */ + public void setDisconnectionSqlCodes(Collection<String> disconnectionSqlCodes) { + if (disconnectionSqlCodes != null && disconnectionSqlCodes.size() > 0) { + HashSet<String> newVal = null; + for (String s : disconnectionSqlCodes) { + if (s != null && s.trim().length() > 0) { + if (newVal == null) { + newVal = new HashSet<String>(); + } + newVal.add(s); + } + } + this.disconnectionSqlCodes = newVal; + } else { + this.disconnectionSqlCodes = null; + } + } + + private boolean fastFailValidation; + + /** + * True means that validation will fail immediately for connections that + * have previously thrown SQLExceptions with SQL_STATE indicating fatal + * disconnection errors. + * + * @return true if connections created by this datasource will fast fail validation. + * @see #setDisconnectionSqlCodes(Collection) + * @since 2.1 + */ + @Override + public boolean isFastFailValidation() { + return fastFailValidation; + } + + /** + * @see #isFastFailValidation() + * @param fastFailValidation true means connections created by this factory will + * fast fail validation + * @since 2.1 + */ + public void setFastFailValidation(boolean fastFailValidation) { + this.fastFailValidation = fastFailValidation; + } // ----------------------------------------------------- Instance Variables Modified: commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java?rev=1655301&r1=1655300&r2=1655301&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java (original) +++ commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java Wed Jan 28 13:33:18 2015 @@ -98,6 +98,12 @@ public class BasicDataSourceFactory impl private static final String PROP_ROLLBACK_ON_RETURN = "rollbackOnReturn"; private static final String PROP_ENABLE_AUTOCOMMIT_ON_RETURN = "enableAutoCommitOnReturn"; private static final String PROP_DEFAULT_QUERYTIMEOUT = "defaultQueryTimeout"; + private static final String PROP_FASTFAIL_VALIDATION = "fastFailValidation"; + + /** + * Value string must be of the form [STATE_CODE;]* + */ + private static final String PROP_DISCONNECTION_SQL_CODES = "disconnectionSqlCodes"; private static final String[] ALL_PROPERTIES = { PROP_DEFAULTAUTOCOMMIT, @@ -139,7 +145,9 @@ public class BasicDataSourceFactory impl PROP_LOGEXPIREDCONNECTIONS, PROP_ROLLBACK_ON_RETURN, PROP_ENABLE_AUTOCOMMIT_ON_RETURN, - PROP_DEFAULT_QUERYTIMEOUT + PROP_DEFAULT_QUERYTIMEOUT, + PROP_FASTFAIL_VALIDATION, + PROP_DISCONNECTION_SQL_CODES }; // -------------------------------------------------- ObjectFactory Methods @@ -389,15 +397,7 @@ public class BasicDataSourceFactory impl value = properties.getProperty(PROP_CONNECTIONINITSQLS); if (value != null) { - StringTokenizer tokenizer = new StringTokenizer(value, ";"); - // Have to jump through these hoops as StringTokenizer implements - // Enumeration<Object> rather than Enumeration<String> - Collection<String> tokens = - new ArrayList<>(tokenizer.countTokens()); - while (tokenizer.hasMoreTokens()) { - tokens.add(tokenizer.nextToken()); - } - dataSource.setConnectionInitSqls(tokens); + dataSource.setConnectionInitSqls(parseList(value, ';')); } value = properties.getProperty(PROP_CONNECTIONPROPERTIES); @@ -414,7 +414,7 @@ public class BasicDataSourceFactory impl if (value != null) { dataSource.setMaxConnLifetimeMillis(Long.parseLong(value)); } - + value = properties.getProperty(PROP_LOGEXPIREDCONNECTIONS); if (value != null) { dataSource.setLogExpiredConnections(Boolean.valueOf(value).booleanValue()); @@ -440,6 +440,15 @@ public class BasicDataSourceFactory impl dataSource.setDefaultQueryTimeout(Integer.valueOf(value)); } + value = properties.getProperty(PROP_FASTFAIL_VALIDATION); + if (value != null) { + dataSource.setFastFailValidation(Boolean.valueOf(value).booleanValue()); + } + + value = properties.getProperty(PROP_DISCONNECTION_SQL_CODES); + if (value != null) { + dataSource.setDisconnectionSqlCodes(parseList(value, ',')); + } // DBCP-215 // Trick to make sure that initialSize connections are created @@ -465,4 +474,20 @@ public class BasicDataSourceFactory impl } return p; } + + /** + * Parse list of property values from a delimited string + * @param value delimited list of values + * @param delimiter character used to separate values in the list + * @return String Collection of values + */ + private static Collection<String> parseList(String value, char delimiter) { + StringTokenizer tokenizer = new StringTokenizer(value, Character.toString(delimiter)); + Collection<String> tokens = + new ArrayList<String>(tokenizer.countTokens()); + while (tokenizer.hasMoreTokens()) { + tokens.add(tokenizer.nextToken()); + } + return tokens; + } } Modified: commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceMXBean.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceMXBean.java?rev=1655301&r1=1655300&r2=1655301&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceMXBean.java (original) +++ commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceMXBean.java Wed Jan 28 13:33:18 2015 @@ -215,10 +215,11 @@ public interface BasicDataSourceMXBean { * @return {@link BasicDataSource#getMaxConnLifetimeMillis()} */ long getMaxConnLifetimeMillis(); - + /** * See {@link BasicDataSource#isLogExpiredConnections()} * @return {@link BasicDataSource#isLogExpiredConnections()} + * @since 2.1 */ boolean isLogExpiredConnections(); @@ -251,4 +252,18 @@ public interface BasicDataSourceMXBean { * @return {@link BasicDataSource#isClosed()} */ boolean isClosed(); + + /** + * See {@link BasicDataSource#isFastFailValidation()} + * @return {@link BasicDataSource#isFastFailValidation()} + * @since 2.1 + */ + boolean isFastFailValidation(); + + /** + * See {@link BasicDataSource#getDisconnectionSqlCodesAsArray()} + * @return {@link BasicDataSource#getDisconnectionSqlCodesAsArray()} + * @since 2.1 + */ + String[] getDisconnectionSqlCodesAsArray(); } Modified: commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java?rev=1655301&r1=1655300&r2=1655301&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java (original) +++ commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java Wed Jan 28 13:33:18 2015 @@ -21,6 +21,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Collection; import javax.management.InstanceAlreadyExistsException; import javax.management.InstanceNotFoundException; @@ -45,7 +46,7 @@ import org.apache.commons.pool2.ObjectPo public class PoolableConnection extends DelegatingConnection<Connection> implements PoolableConnectionMXBean { - private static MBeanServer MBEAN_SERVER = null; + private static MBeanServer MBEAN_SERVER = null; static { try { @@ -56,7 +57,7 @@ public class PoolableConnection extends } /** The pool to which I should return. */ - private ObjectPool<PoolableConnection> _pool = null; + private final ObjectPool<PoolableConnection> _pool; private final ObjectName _jmxName; @@ -66,15 +67,38 @@ public class PoolableConnection extends private String lastValidationSql = null; /** + * Indicate that unrecoverable SQLException was thrown when using this connection. + * Such a connection should be considered broken and not pass validation in the future. + */ + private boolean _fatalSqlExceptionThrown = false; + + /** + * SQL_STATE codes considered to signal fatal conditions. Overrides the + * defaults in {@link Utils#DISCONNECTION_SQL_CODES} (plus anything starting + * with {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). + */ + private final Collection<String> _disconnectionSqlCodes; + + /** Whether or not to fast fail validation after fatal connection errors */ + private final boolean _fastFailValidation; + + /** * * @param conn my underlying connection * @param pool the pool to which I should return when closed + * @param jmxName JMX name + * @param disconnectSqlCodes SQL_STATE codes considered fatal disconnection errors + * @param fastFailValidation true means fatal disconnection errors cause subsequent + * validations to fail immediately (no attempt to run query or isValid) */ public PoolableConnection(Connection conn, - ObjectPool<PoolableConnection> pool, ObjectName jmxName) { + ObjectPool<PoolableConnection> pool, ObjectName jmxName, Collection<String> disconnectSqlCodes, + boolean fastFailValidation) { super(conn); _pool = pool; _jmxName = jmxName; + _disconnectionSqlCodes = disconnectSqlCodes; + _fastFailValidation = fastFailValidation; if (jmxName != null) { try { @@ -86,6 +110,17 @@ public class PoolableConnection extends } } + /** + * + * @param conn my underlying connection + * @param pool the pool to which I should return when closed + * @param jmxName JMX name + */ + public PoolableConnection(Connection conn, + ObjectPool<PoolableConnection> pool, ObjectName jmxName) { + this(conn, pool, jmxName, null, false); + } + @Override protected void passivate() throws SQLException { @@ -218,8 +253,29 @@ public class PoolableConnection extends return toString(); } - + /** + * Validates the connection, using the following algorithm: + * <ol> + * <li>If {@code fastFailValidation} (constructor argument) is {@code true} and + * this connection has previously thrown a fatal disconnection exception, + * a {@code SQLException} is thrown. </li> + * <li>If {@code sql} is null, the driver's + * #{@link Connection#isValid(int) isvalid(timeout)} is called. + * If it returns {@code false}, {@code SQLException} is thrown; + * otherwise, this method returns successfully.</li> + * <li>If {@code sql} is not null, it is executed as a query and if the resulting + * {@code ResultSet} contains at least one row, this method returns + * successfully. If not, {@code SQLException} is thrown.</li> + * </ol> + * @param sql validation query + * @param timeout validation timeout + * @throws SQLException if validation fails or an SQLException occurs during validation + */ public void validate(String sql, int timeout) throws SQLException { + if (_fastFailValidation && _fatalSqlExceptionThrown) { + throw new SQLException(Utils.getMessage("poolableConnection.validate.fastFail")); + } + if (sql == null || sql.length() == 0) { if (timeout < 0) { timeout = 0; @@ -250,5 +306,38 @@ public class PoolableConnection extends throw sqle; } } + + /** + * Checks the SQLState of the input exception and any nested SQLExceptions it wraps. + * <p> + * If {@link #getDisconnectSqlCodes() disconnectSQLCodes} 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 + */ + private boolean isDisconnectionSqlException(SQLException e) { + boolean fatalException = false; + 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) { + if (e.getNextException() != null) { + fatalException = isDisconnectionSqlException(e.getNextException()); + } + } + } + return fatalException; + } + + @Override + protected void handleException(SQLException e) throws SQLException { + _fatalSqlExceptionThrown |= isDisconnectionSqlException(e); + super.handleException(e); + } } Modified: commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java?rev=1655301&r1=1655300&r2=1655301&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java (original) +++ commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java Wed Jan 28 13:33:18 2015 @@ -190,7 +190,6 @@ public class PoolableConnectionFactory this.rollbackOnReturn = rollbackOnReturn; } - public Integer getDefaultQueryTimeout() { return defaultQueryTimeout; } @@ -199,6 +198,58 @@ public class PoolableConnectionFactory this.defaultQueryTimeout = defaultQueryTimeout; } + /** + * SQL_STATE codes considered to signal fatal conditions. + * <p> + * Overrides the defaults in {@link Utils#DISCONNECTION_SQL_CODES} + * (plus anything starting with {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). + * If this property is non-null and {@link #isFastFailValidation()} is + * {@code true}, whenever connections created by this factory generate exceptions + * with SQL_STATE codes in this list, they will be marked as "fatally disconnected" + * and subsequent validations will fail fast (no attempt at isValid or validation + * query).</p> + * <p> + * If {@link #isFastFailValidation()} is {@code false} setting this property has no + * effect.</p> + * + * @return SQL_STATE codes overriding defaults + * @since 2.1 + */ + public Collection<String> getDisconnectionSqlCodes() { + return _disconnectionSqlCodes; + } + + /** + * @see #getDisconnectionSqlCodes() + * @param disconnectionSqlCodes + * @since 2.1 + */ + public void setDisconnectionSqlCodes(Collection<String> disconnectionSqlCodes) { + _disconnectionSqlCodes = disconnectionSqlCodes; + } + + /** + * True means that validation will fail immediately for connections that + * have previously thrown SQLExceptions with SQL_STATE indicating fatal + * disconnection errors. + * + * @return true if connections created by this factory will fast fail validation. + * @see #setDisconnectionSqlCodes(Collection) + * @since 2.1 + */ + public boolean isFastFailValidation() { + return _fastFailValidation; + } + + /** + * @see #isFastFailValidation() + * @param fastFailValidation true means connections created by this factory will + * fast fail validation + * @since 2.1 + */ + public void setFastFailValidation(boolean fastFailValidation) { + _fastFailValidation = fastFailValidation; + } @Override public PooledObject<PoolableConnection> makeObject() throws Exception { @@ -251,7 +302,8 @@ public class PoolableConnectionFactory Constants.JMX_CONNECTION_BASE_EXT + connIndex); } - PoolableConnection pc = new PoolableConnection(conn,_pool, connJmxName); + PoolableConnection pc = new PoolableConnection(conn,_pool, connJmxName, + _disconnectionSqlCodes, _fastFailValidation); return new DefaultPooledObject<>(pc); } @@ -396,6 +448,8 @@ public class PoolableConnectionFactory private volatile String _validationQuery = null; private volatile int _validationQueryTimeout = -1; private Collection<String> _connectionInitSqls = null; + private Collection<String> _disconnectionSqlCodes = null; + private boolean _fastFailValidation = false; private volatile ObjectPool<PoolableConnection> _pool = null; private Boolean _defaultReadOnly = null; private Boolean _defaultAutoCommit = null; Modified: commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/Utils.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/Utils.java?rev=1655301&r1=1655300&r2=1655301&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/Utils.java (original) +++ commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/Utils.java Wed Jan 28 13:33:18 2015 @@ -22,7 +22,9 @@ import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; import java.text.MessageFormat; +import java.util.HashSet; import java.util.ResourceBundle; +import java.util.Set; /** * Utility methods. @@ -37,6 +39,31 @@ public final class Utils { public static final boolean IS_SECURITY_ENABLED = System.getSecurityManager() != null; + /** Any SQL_STATE starting with this value is considered a fatal disconnect */ + public static final String DISCONNECTION_SQL_CODE_PREFIX = "08"; + + /** + * SQL codes of fatal connection errors. + * <ul> + * <li>57P01 (ADMIN SHUTDOWN)</li> + * <li>57P02 (CRASH SHUTDOWN)</li> + * <li>57P03 (CANNOT CONNECT NOW)</li> + * <li>01002 (SQL92 disconnect error)</li> + * <li>JZ0C0 (Sybase disconnect error)</li> + * <li>JZ0C1 (Sybase disconnect error)</li> + * </ul> + */ + public static final Set<String> DISCONNECTION_SQL_CODES; + + static { + DISCONNECTION_SQL_CODES = new HashSet<String>(); + DISCONNECTION_SQL_CODES.add("57P01"); // ADMIN SHUTDOWN + DISCONNECTION_SQL_CODES.add("57P02"); // CRASH SHUTDOWN + DISCONNECTION_SQL_CODES.add("57P03"); // CANNOT CONNECT NOW + DISCONNECTION_SQL_CODES.add("01002"); // SQL92 disconnect error + DISCONNECTION_SQL_CODES.add("JZ0C0"); // Sybase disconnect error + DISCONNECTION_SQL_CODES.add("JZ0C1"); // Sybase disconnect error + } private Utils() { // not instantiable Modified: commons/proper/dbcp/trunk/src/main/resources/org/apache/commons/dbcp2/LocalStrings.properties URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/main/resources/org/apache/commons/dbcp2/LocalStrings.properties?rev=1655301&r1=1655300&r2=1655301&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/main/resources/org/apache/commons/dbcp2/LocalStrings.properties (original) +++ commons/proper/dbcp/trunk/src/main/resources/org/apache/commons/dbcp2/LocalStrings.properties Wed Jan 28 13:33:18 2015 @@ -15,9 +15,11 @@ connectionFactory.lifetimeExceeded=The lifetime of the connection [{0}] milliseconds exceeds the maximum permitted value of [{1}] milliseconds -poolableConnectionFactory.validateObject.fail=Failed to validate a poolable connection +poolableConnectionFactory.validateObject.fail=Failed to validate a poolable connection. -swallowedExceptionLogger.onSwallowedException=An internal object pool swallowed an Exception +poolableConnection.validate.fastFail=Fatal SQLException was thrown previously on this connection. + +swallowedExceptionLogger.onSwallowedException=An internal object pool swallowed an Exception. poolingDataSource.factoryConfig=PoolableConnectionFactory not linked to pool. Calling setPool() to fix the configuration. Modified: commons/proper/dbcp/trunk/src/site/xdoc/configuration.xml URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/site/xdoc/configuration.xml?rev=1655301&r1=1655300&r2=1655301&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/site/xdoc/configuration.xml (original) +++ commons/proper/dbcp/trunk/src/site/xdoc/configuration.xml Wed Jan 28 13:33:18 2015 @@ -363,8 +363,8 @@ value less than the maximum number of cu </source> <p> <img src="images/icon_info_sml.gif"/> -Default is false, it is a potential dangerous operation and misbehaving programs can do harmfull things. (closing the underlying or continue using it when the guarded connection is already closed) -Be carefull and only use when you need direct access to driver specific extentions. +Default is false, it is a potential dangerous operation and misbehaving programs can do harmful things. (closing the underlying or continue using it when the guarded connection is already closed) +Be careful and only use when you need direct access to driver specific extensions. </p> <p> <img src="images/icon_warning_sml.gif"/> @@ -420,6 +420,39 @@ default (300 sec). Traversing a resultse or CallableStatement or using one of these to execute a query (using one of the execute methods) resets the lastUsed property of the parent connection. </p> +<table> +<hr><th>Parameter</th><th>Default</th><th>Description</th></hr> +<tr> + <td>fastFailValidation</td> + <td>false</td> + <td> + When this property is true, validation fails fast for connections that have + thrown "fatal" SQLExceptions. Requests to validate disconnected connections + fail immediately, with no call to the driver's isValid method or attempt to + execute a validation query.<br/> + The SQL_STATE codes considered to signal fatal errors are by default the following: + <ul> + <li>57P01 (ADMIN SHUTDOWN)</li> + <li>57P02 (CRASH SHUTDOWN)</li> + <li>57P03 (CANNOT CONNECT NOW)</li> + <li>01002 (SQL92 disconnect error)</li> + <li>JZ0C0 (Sybase disconnect error)</li> + <li>JZ0C1 (Sybase disconnect error)</li> + <li>Any SQL_STATE code that starts with "08"</li> + </ul> + To override this default set of disconnection codes, set the + <code>disconnectionSqlCodes</code> property. + </td> +</tr> +<tr> + <td>disconnectionSqlCodes</td> + <td>null</td> + <td>Comma-delimited list of SQL_STATE codes considered to signal fatal disconnection + errors. Setting this property has no effect unless + <code>fastFailValidation</code> is set to <code>true.</code> + </td> +</tr> +</table> </section> Modified: commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestBasicDataSourceFactory.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestBasicDataSourceFactory.java?rev=1655301&r1=1655300&r2=1655301&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestBasicDataSourceFactory.java (original) +++ commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestBasicDataSourceFactory.java Wed Jan 28 13:33:18 2015 @@ -19,6 +19,7 @@ package org.apache.commons.dbcp2; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import java.sql.Connection; import java.util.Properties; @@ -74,6 +75,8 @@ public class TestBasicDataSourceFactory properties.setProperty("poolPreparedStatements", "true"); properties.setProperty("maxOpenPreparedStatements", "10"); properties.setProperty("lifo", "true"); + properties.setProperty("fastFailValidation", "true"); + properties.setProperty("disconnectionSqlCodes", "XXX,YYY"); BasicDataSource ds = BasicDataSourceFactory.createDataSource(properties); @@ -111,5 +114,8 @@ public class TestBasicDataSourceFactory assertEquals(true, ds.isPoolPreparedStatements()); assertEquals(10, ds.getMaxOpenPreparedStatements()); assertEquals(true, ds.getLifo()); + assertEquals(true, ds.isFastFailValidation()); + assertTrue(ds.getDisconnectionSqlCodes().contains("XXX")); + assertTrue(ds.getDisconnectionSqlCodes().contains("YYY")); } } Modified: commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestPoolableConnection.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestPoolableConnection.java?rev=1655301&r1=1655300&r2=1655301&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestPoolableConnection.java (original) +++ commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestPoolableConnection.java Wed Jan 28 13:33:18 2015 @@ -18,12 +18,14 @@ package org.apache.commons.dbcp2; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; import java.sql.Connection; import java.sql.SQLException; +import java.util.ArrayList; -import org.apache.commons.pool2.ObjectPool; import org.apache.commons.pool2.impl.GenericObjectPool; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -34,7 +36,7 @@ import org.junit.Test; */ public class TestPoolableConnection { - private ObjectPool<PoolableConnection> pool = null; + private GenericObjectPool<PoolableConnection> pool = null; @Before public void setUp() throws Exception { @@ -45,9 +47,15 @@ public class TestPoolableConnection { factory.setDefaultAutoCommit(Boolean.TRUE); factory.setDefaultReadOnly(Boolean.TRUE); + pool = new GenericObjectPool<>(factory); factory.setPool(pool); } + + @After + public void tearDown() { + pool.close(); + } @Test public void testConnectionPool() throws Exception { @@ -108,4 +116,82 @@ public class TestPoolableConnection { Assert.assertEquals(0, pool.getNumActive()); Assert.assertEquals(1, pool.getNumIdle()); } + + @Test + public void testFastFailValidation() throws Exception { + pool.setTestOnReturn(true); + PoolableConnectionFactory factory = (PoolableConnectionFactory) pool.getFactory(); + factory.setFastFailValidation(true); + PoolableConnection conn = pool.borrowObject(); + TesterConnection nativeConnection = (TesterConnection) conn.getInnermostDelegate(); + + // Set up non-fatal exception + nativeConnection.setFailure(new SQLException("Not fatal error.", "Invalid syntax.")); + try { + conn.createStatement(); + fail("Should throw SQL exception."); + } catch (SQLException ignored) { + // cleanup failure + nativeConnection.setFailure(null); + } + + // validate should not fail - error was not fatal and condition was cleaned up + conn.validate("SELECT 1", 1000); + + // now set up fatal failure + nativeConnection.setFailure(new SQLException("Fatal connection error.", "01002")); + + try { + conn.createStatement(); + fail("Should throw SQL exception."); + } catch (SQLException ignored) { + // cleanup failure + nativeConnection.setFailure(null); + } + + // validate should now fail because of previous fatal error, despite cleanup + try { + conn.validate("SELECT 1", 1000); + fail("Should throw SQL exception on validation."); + } catch (SQLException notValid){ + // expected - fatal error && fastFailValidation + } + + // verify that bad connection does not get returned to the pool + conn.close(); // testOnReturn triggers validate, which should fail + assertEquals("The pool should have no active connections", + 0, pool.getNumActive()); + assertEquals("The pool should have no idle connections", + 0, pool.getNumIdle()); + } + + @Test + public void testFastFailValidationCustomCodes() throws Exception { + pool.setTestOnReturn(true); + PoolableConnectionFactory factory = (PoolableConnectionFactory) pool.getFactory(); + factory.setFastFailValidation(true); + ArrayList<String> disconnectionSqlCodes = new ArrayList<String>(); + disconnectionSqlCodes.add("XXX"); + factory.setDisconnectionSqlCodes(disconnectionSqlCodes); + PoolableConnection conn = pool.borrowObject(); + TesterConnection nativeConnection = (TesterConnection) conn.getInnermostDelegate(); + + // Set up fatal exception + nativeConnection.setFailure(new SQLException("Fatal connection error.", "XXX")); + + try { + conn.createStatement(); + fail("Should throw SQL exception."); + } catch (SQLException ignored) { + // cleanup failure + nativeConnection.setFailure(null); + } + + // verify that bad connection does not get returned to the pool + conn.close(); // testOnReturn triggers validate, which should fail + assertEquals("The pool should have no active connections", + 0, pool.getNumActive()); + assertEquals("The pool should have no idle connections", + 0, pool.getNumIdle()); + } } Modified: commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TesterConnection.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TesterConnection.java?rev=1655301&r1=1655300&r2=1655301&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TesterConnection.java (original) +++ commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TesterConnection.java Wed Jan 28 13:33:18 2015 @@ -234,7 +234,11 @@ public class TesterConnection implements protected void checkFailure() throws SQLException { if (failure != null) { - throw new SQLException("TesterConnection failure", failure); + if(failure instanceof SQLException) { + throw (SQLException)failure; + } else { + throw new SQLException("TesterConnection failure", failure); + } } }