--- src/main/java/org/apache/tomcat/jdbc/pool/PoolConfiguration.java (revision 1456054) +++ src/main/java/org/apache/tomcat/jdbc/pool/PoolConfiguration.java (working copy) @@ -524,6 +524,19 @@ public void setValidationQuery(String validationQuery); /** + * The timeout in seconds before a connection validation queries fail. + * A value less than or equal to zero will disable this feature. Defaults to -1. + * @return the timeout value in seconds + */ + public int getValidationQueryTimeout(); + + /** + * The timeout in seconds before a connection validation queries fail. + * A value less than or equal to zero will disable this feature. Defaults to -1. + */ + public void setValidationQueryTimeout(int validationQueryTimeout); + + /** * Return the name of the optional validator class - may be null. * * @return the name of the optional validator class - may be null --- src/main/java/org/apache/tomcat/jdbc/pool/PooledConnection.java (revision 1456054) +++ src/main/java/org/apache/tomcat/jdbc/pool/PooledConnection.java (working copy) @@ -451,6 +451,12 @@ Statement stmt = null; try { stmt = connection.createStatement(); + + int validationQueryTimeout = poolProperties.getValidationQueryTimeout(); + if (validationQueryTimeout > 0) { + stmt.setQueryTimeout(validationQueryTimeout); + } + stmt.execute(query); stmt.close(); this.lastValidated = now; --- src/main/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java (revision 1456054) +++ src/main/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java (working copy) @@ -310,6 +310,11 @@ public String getValidationQuery() { return getPoolProperties().getValidationQuery(); } + + @Override + public int getValidationQueryTimeout() { + return getPoolProperties().getValidationQueryTimeout(); + } /** * {@inheritDoc} @@ -664,6 +669,11 @@ getPoolProperties().setValidationQuery(validationQuery); } + @Override + public void setValidationQueryTimeout(int validationQueryTimeout) { + getPoolProperties().setValidationQueryTimeout(validationQueryTimeout); + } + /** * {@inheritDoc} */ --- src/main/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java (revision 1456054) +++ src/main/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java (working copy) @@ -80,6 +80,7 @@ protected static final String PROP_TESTWHILEIDLE = "testWhileIdle"; protected static final String PROP_TESTONCONNECT = "testOnConnect"; protected static final String PROP_VALIDATIONQUERY = "validationQuery"; + protected static final String PROP_VALIDATIONQUERY_TIMEOUT = "validationQueryTimeout"; protected static final String PROP_VALIDATOR_CLASS_NAME = "validatorClassName"; protected static final String PROP_NUMTESTSPEREVICTIONRUN = "numTestsPerEvictionRun"; @@ -149,6 +150,7 @@ PROP_URL, PROP_USERNAME, PROP_VALIDATIONQUERY, + PROP_VALIDATIONQUERY_TIMEOUT, PROP_VALIDATOR_CLASS_NAME, PROP_VALIDATIONINTERVAL, PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED, @@ -366,6 +368,11 @@ if (value != null) { poolProperties.setValidationQuery(value); } + + value = properties.getProperty(PROP_VALIDATIONQUERY_TIMEOUT); + if (value != null) { + poolProperties.setValidationQueryTimeout(Integer.parseInt(value)); + } value = properties.getProperty(PROP_VALIDATOR_CLASS_NAME); if (value != null) { --- src/main/java/org/apache/tomcat/jdbc/pool/PoolProperties.java (revision 1456054) +++ src/main/java/org/apache/tomcat/jdbc/pool/PoolProperties.java (working copy) @@ -57,6 +57,7 @@ private volatile int minIdle = initialSize; private volatile int maxWait = 30000; private volatile String validationQuery; + private volatile int validationQueryTimeout = -1; private volatile String validatorClassName; private volatile Validator validator; private volatile boolean testOnBorrow = false; @@ -382,6 +383,22 @@ /** * {@inheritDoc} */ + @Override + public int getValidationQueryTimeout() { + return validationQueryTimeout; + } + + /** + * {@inheritDoc} + */ + @Override + public void setValidationQueryTimeout(int validationQueryTimeout) { + this.validationQueryTimeout = validationQueryTimeout; + } + + /** + * {@inheritDoc} + */ @Override public String getValidatorClassName() { --- src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java (revision 1456054) +++ src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java (working copy) @@ -452,6 +452,15 @@ public void setValidatorClassName(String className) { this.poolProperties.setValidatorClassName(className); } + + /** + * {@inheritDoc} + */ + + @Override + public void setValidationQueryTimeout(int validationQueryTimeout) { + this.poolProperties.setValidationQueryTimeout(validationQueryTimeout); + } /** * {@inheritDoc} @@ -928,6 +937,15 @@ /** * {@inheritDoc} */ + + @Override + public int getValidationQueryTimeout() { + return getPoolProperties().getValidationQueryTimeout(); + } + + /** + * {@inheritDoc} + */ @Override public String getValidatorClassName() { --- src/main/java/org/apache/tomcat/jdbc/pool/mbeans-descriptors.xml (revision 1456054) +++ src/main/java/org/apache/tomcat/jdbc/pool/mbeans-descriptors.xml (working copy) @@ -125,6 +125,11 @@ description="The query to run during validation" type="java.lang.String" writeable="false"/> + + SELECT 1(mysql), select 1 from dual(oracle), SELECT 1(MS Sql Server)

+ + +

(int) The timeout in seconds before a connection validation queries fail. This works by calling + java.sql.Statement.setQueryTimeout(seconds) on the statement that executes the validationQuery. + The pool itself doesn't timeout the query, it is still up to the JDBC driver to enforce query timeouts. + A value less than or equal to zero will disable this feature. + The default value is -1. +

+

(String) The name of a class which implements the --- src/test/java/org/apache/tomcat/jdbc/test/TestValidationQueryTimeout.java (revision 0) +++ src/test/java/org/apache/tomcat/jdbc/test/TestValidationQueryTimeout.java (revision 0) @@ -0,0 +1,133 @@ +package org.apache.tomcat.jdbc.test; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.DriverPropertyInfo; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.Statement; +import java.util.Properties; +import java.util.logging.Logger; + +import junit.framework.Assert; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class TestValidationQueryTimeout extends DefaultTestCase { + + private static final int TIMEOUT = 10; + private static boolean isTimeoutSet; + + @Before + public void setUp() throws SQLException { + DriverManager.registerDriver(new MockDriver()); + isTimeoutSet = false; + } + + @After + public void tearDown() throws SQLException { + DriverManager.deregisterDriver(new MockDriver()); + } + + @Test + public void testValidationQueryTimeoutEnabled() throws Exception { + // use our mock driver + this.datasource.setDriverClassName(MockDriver.class.getName()); + this.datasource.setUrl(MockDriver.url); + + // Required to trigger validation query's execution + this.datasource.setTestOnBorrow(true); + this.datasource.setValidationInterval(-1); + this.datasource.setValidationQuery("SELECT 1"); + this.datasource.setValidationQueryTimeout(TIMEOUT); + + // because testOnBorrow is true, this triggers the validation query + this.datasource.getConnection(); + Assert.assertTrue(isTimeoutSet); + } + + @Test + public void testValidationQueryTimeoutDisabled() throws Exception { + // use our mock driver + this.datasource.setDriverClassName(MockDriver.class.getName()); + this.datasource.setUrl(MockDriver.url); + + // Required to trigger validation query's execution + this.datasource.setTestOnBorrow(true); + this.datasource.setValidationInterval(-1); + this.datasource.setValidationQuery("SELECT 1"); + this.datasource.setValidationQueryTimeout(-1); + + // because testOnBorrow is true, this triggers the validation query + this.datasource.getConnection(); + Assert.assertFalse(isTimeoutSet); + } + + /** + * Mock Driver, Connection and Statement implementations use to verify setQueryTimeout was called. + */ + public static class MockDriver implements java.sql.Driver { + public static final String url = "jdbc:tomcat:mock"; + + public MockDriver() { + } + + @Override + public boolean acceptsURL(String url) throws SQLException { + return url!=null && url.equals(MockDriver.url); + } + + @Override + public Connection connect(String url, Properties info) throws SQLException { + return new MockConnection(info); + } + + @Override + public int getMajorVersion() { + return 0; + } + + @Override + public int getMinorVersion() { + return 0; + } + + @Override + public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { + return null; + } + + @Override + public boolean jdbcCompliant() { + return false; + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + return null; + } + } + + public static class MockConnection extends org.apache.tomcat.jdbc.test.driver.Connection { + public MockConnection(Properties info) { + super(info); + } + + @Override + public Statement createStatement() throws SQLException { + return new MockStatement(); + } + } + + public static class MockStatement extends org.apache.tomcat.jdbc.test.driver.Statement { + @Override + public void setQueryTimeout(int seconds) throws SQLException { + super.setQueryTimeout(seconds); + Assert.assertEquals(TIMEOUT, seconds); + isTimeoutSet = true; + } + } + +}