summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorApollon Oikonomopoulos <apoikos@debian.org>2017-03-01 14:21:59 +0200
committerApollon Oikonomopoulos <apoikos@debian.org>2017-03-01 14:21:59 +0200
commitbd7b6679cea5620446718911de7a6764f81a9a7a (patch)
treea725b29afd153ae0d18c47e41c1197e2b409313d /src
New upstream version 2.6.0
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/zaxxer/hikari/HikariConfig.java988
-rw-r--r--src/main/java/com/zaxxer/hikari/HikariConfigMXBean.java180
-rw-r--r--src/main/java/com/zaxxer/hikari/HikariDataSource.java356
-rw-r--r--src/main/java/com/zaxxer/hikari/HikariJNDIFactory.java101
-rw-r--r--src/main/java/com/zaxxer/hikari/HikariPoolMXBean.java39
-rw-r--r--src/main/java/com/zaxxer/hikari/hibernate/HikariConfigurationUtil.java70
-rw-r--r--src/main/java/com/zaxxer/hikari/hibernate/HikariConnectionProvider.java156
-rw-r--r--src/main/java/com/zaxxer/hikari/metrics/IMetricsTracker.java34
-rwxr-xr-xsrc/main/java/com/zaxxer/hikari/metrics/MetricsTracker.java27
-rw-r--r--src/main/java/com/zaxxer/hikari/metrics/MetricsTrackerFactory.java29
-rw-r--r--src/main/java/com/zaxxer/hikari/metrics/PoolStats.java95
-rwxr-xr-xsrc/main/java/com/zaxxer/hikari/metrics/dropwizard/CodaHaleMetricsTracker.java144
-rw-r--r--src/main/java/com/zaxxer/hikari/metrics/dropwizard/CodahaleHealthChecker.java131
-rw-r--r--src/main/java/com/zaxxer/hikari/metrics/dropwizard/CodahaleMetricsTrackerFactory.java43
-rw-r--r--src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java55
-rw-r--r--src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java106
-rw-r--r--src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java39
-rwxr-xr-xsrc/main/java/com/zaxxer/hikari/pool/HikariPool.java719
-rwxr-xr-xsrc/main/java/com/zaxxer/hikari/pool/PoolBase.java703
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/PoolEntry.java203
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyCallableStatement.java37
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java475
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyFactory.java80
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyLeakTask.java100
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyPreparedStatement.java71
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyResultSet.java106
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyStatement.java240
-rw-r--r--src/main/java/com/zaxxer/hikari/util/ClockSource.java313
-rwxr-xr-xsrc/main/java/com/zaxxer/hikari/util/ConcurrentBag.java385
-rw-r--r--src/main/java/com/zaxxer/hikari/util/DriverDataSource.java145
-rw-r--r--src/main/java/com/zaxxer/hikari/util/FastList.java368
-rw-r--r--src/main/java/com/zaxxer/hikari/util/JavassistProxyFactory.java278
-rw-r--r--src/main/java/com/zaxxer/hikari/util/PropertyElf.java157
-rw-r--r--src/main/java/com/zaxxer/hikari/util/QueuedSequenceSynchronizer.java137
-rw-r--r--src/main/java/com/zaxxer/hikari/util/Sequence.java71
-rw-r--r--src/main/java/com/zaxxer/hikari/util/SuspendResumeLock.java79
-rw-r--r--src/main/java/com/zaxxer/hikari/util/UtilityElf.java195
-rw-r--r--src/test/java/com/zaxxer/hikari/db/BasicPoolTest.java149
-rwxr-xr-xsrc/test/java/com/zaxxer/hikari/metrics/dropwizard/CodaHaleMetricsTrackerTest.java39
-rw-r--r--src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java125
-rw-r--r--src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java78
-rw-r--r--src/test/java/com/zaxxer/hikari/mocks/MockDataSource.java162
-rw-r--r--src/test/java/com/zaxxer/hikari/mocks/StubBaseConnection.java47
-rw-r--r--src/test/java/com/zaxxer/hikari/mocks/StubConnection.java486
-rwxr-xr-xsrc/test/java/com/zaxxer/hikari/mocks/StubDataSource.java149
-rw-r--r--src/test/java/com/zaxxer/hikari/mocks/StubDriver.java96
-rw-r--r--src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java671
-rw-r--r--src/test/java/com/zaxxer/hikari/mocks/StubResultSet.java1292
-rw-r--r--src/test/java/com/zaxxer/hikari/mocks/StubStatement.java387
-rw-r--r--src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java91
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/ConcurrentCloseConnectionTest.java76
-rwxr-xr-xsrc/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java198
-rwxr-xr-xsrc/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java106
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/ConnectionStateTest.java170
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/ExceptionTest.java119
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java84
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/IsolationTest.java74
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/JdbcDriverTest.java86
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/MiscTest.java134
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/PostgresTest.java231
-rwxr-xr-xsrc/test/java/com/zaxxer/hikari/pool/RampUpDown.java75
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/ShutdownTest.java356
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/StatementTest.java119
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestConcurrentBag.java117
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestConnectionCloseBlocking.java98
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java277
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestConnections.java621
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestElf.java177
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestHibernate.java57
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestJNDI.java146
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestMBean.java44
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestMetrics.java303
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestPropertySetter.java111
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestProxies.java324
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestValidation.java253
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/UnwrapTest.java90
-rw-r--r--src/test/java/com/zaxxer/hikari/util/ClockSourceTest.java60
-rw-r--r--src/test/java/com/zaxxer/hikari/util/TestFastList.java172
-rw-r--r--src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java328
-rw-r--r--src/test/resources/hibernate.properties4
-rw-r--r--src/test/resources/log4j2-test.xml13
-rw-r--r--src/test/resources/propfile1.properties4
-rw-r--r--src/test/resources/propfile2.properties5
-rw-r--r--src/test/resources/propfile3.properties6
84 files changed, 16265 insertions, 0 deletions
diff --git a/src/main/java/com/zaxxer/hikari/HikariConfig.java b/src/main/java/com/zaxxer/hikari/HikariConfig.java
new file mode 100644
index 0000000..ef9f9be
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/HikariConfig.java
@@ -0,0 +1,988 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari;
+
+import static com.zaxxer.hikari.util.UtilityElf.getNullIfEmpty;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.sql.DataSource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.health.HealthCheckRegistry;
+import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
+import com.zaxxer.hikari.util.PropertyElf;
+
+public class HikariConfig implements HikariConfigMXBean
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(HikariConfig.class);
+
+ private static final long CONNECTION_TIMEOUT = SECONDS.toMillis(30);
+ private static final long VALIDATION_TIMEOUT = SECONDS.toMillis(5);
+ private static final long IDLE_TIMEOUT = MINUTES.toMillis(10);
+ private static final long MAX_LIFETIME = MINUTES.toMillis(30);
+ private static final int DEFAULT_POOL_SIZE = 10;
+
+ private static boolean unitTest;
+
+ // Properties changeable at runtime through the MBean
+ //
+ private volatile long connectionTimeout;
+ private volatile long validationTimeout;
+ private volatile long idleTimeout;
+ private volatile long leakDetectionThreshold;
+ private volatile long maxLifetime;
+ private volatile int maxPoolSize;
+ private volatile int minIdle;
+
+ // Properties NOT changeable at runtime
+ //
+ private long initializationFailTimeout;
+ private String catalog;
+ private String connectionInitSql;
+ private String connectionTestQuery;
+ private String dataSourceClassName;
+ private String dataSourceJndiName;
+ private String driverClassName;
+ private String jdbcUrl;
+ private String password;
+ private String poolName;
+ private String transactionIsolationName;
+ private String username;
+ private boolean isAutoCommit;
+ private boolean isReadOnly;
+ private boolean isIsolateInternalQueries;
+ private boolean isRegisterMbeans;
+ private boolean isAllowPoolSuspension;
+ private DataSource dataSource;
+ private Properties dataSourceProperties;
+ private ThreadFactory threadFactory;
+ private ScheduledExecutorService scheduledExecutor;
+ private MetricsTrackerFactory metricsTrackerFactory;
+ private Object metricRegistry;
+ private Object healthCheckRegistry;
+ private Properties healthCheckProperties;
+
+ /**
+ * Default constructor
+ */
+ public HikariConfig()
+ {
+ dataSourceProperties = new Properties();
+ healthCheckProperties = new Properties();
+
+ minIdle = -1;
+ maxPoolSize = -1;
+ maxLifetime = MAX_LIFETIME;
+ connectionTimeout = CONNECTION_TIMEOUT;
+ validationTimeout = VALIDATION_TIMEOUT;
+ idleTimeout = IDLE_TIMEOUT;
+ initializationFailTimeout = 1;
+ isAutoCommit = true;
+
+ String systemProp = System.getProperty("hikaricp.configurationFile");
+ if (systemProp != null) {
+ loadProperties(systemProp);
+ }
+ }
+
+ /**
+ * Construct a HikariConfig from the specified properties object.
+ *
+ * @param properties the name of the property file
+ */
+ public HikariConfig(Properties properties)
+ {
+ this();
+ PropertyElf.setTargetFromProperties(this, properties);
+ }
+
+ /**
+ * Construct a HikariConfig from the specified property file name. <code>propertyFileName</code>
+ * will first be treated as a path in the file-system, and if that fails the
+ * Class.getResourceAsStream(propertyFileName) will be tried.
+ *
+ * @param propertyFileName the name of the property file
+ */
+ public HikariConfig(String propertyFileName)
+ {
+ this();
+
+ loadProperties(propertyFileName);
+ }
+
+ /**
+ * Get the default catalog name to be set on connections.
+ *
+ * @return the default catalog name
+ */
+ public String getCatalog()
+ {
+ return catalog;
+ }
+
+ /**
+ * Set the default catalog name to be set on connections.
+ *
+ * @param catalog the catalog name, or null
+ */
+ public void setCatalog(String catalog)
+ {
+ this.catalog = catalog;
+ }
+
+ /**
+ * Get the SQL query to be executed to test the validity of connections.
+ *
+ * @return the SQL query string, or null
+ */
+ public String getConnectionTestQuery()
+ {
+ return connectionTestQuery;
+ }
+
+ /**
+ * Set the SQL query to be executed to test the validity of connections. Using
+ * the JDBC4 <code>Connection.isValid()</code> method to test connection validity can
+ * be more efficient on some databases and is recommended. See
+ * {@link HikariConfig#setJdbc4ConnectionTest(boolean)}.
+ *
+ * @param connectionTestQuery a SQL query string
+ */
+ public void setConnectionTestQuery(String connectionTestQuery)
+ {
+ this.connectionTestQuery = connectionTestQuery;
+ }
+
+ /**
+ * Get the SQL string that will be executed on all new connections when they are
+ * created, before they are added to the pool.
+ *
+ * @return the SQL to execute on new connections, or null
+ */
+ public String getConnectionInitSql()
+ {
+ return connectionInitSql;
+ }
+
+ /**
+ * Set the SQL string that will be executed on all new connections when they are
+ * created, before they are added to the pool. If this query fails, it will be
+ * treated as a failed connection attempt.
+ *
+ * @param connectionInitSql the SQL to execute on new connections
+ */
+ public void setConnectionInitSql(String connectionInitSql)
+ {
+ this.connectionInitSql = connectionInitSql;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long getConnectionTimeout()
+ {
+ return connectionTimeout;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setConnectionTimeout(long connectionTimeoutMs)
+ {
+ if (connectionTimeoutMs == 0) {
+ this.connectionTimeout = Integer.MAX_VALUE;
+ }
+ else if (connectionTimeoutMs < 250) {
+ throw new IllegalArgumentException("connectionTimeout cannot be less than 250ms");
+ }
+ else {
+ this.connectionTimeout = connectionTimeoutMs;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long getValidationTimeout()
+ {
+ return validationTimeout;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setValidationTimeout(long validationTimeoutMs)
+ {
+ if (validationTimeoutMs < 250) {
+ throw new IllegalArgumentException("validationTimeout cannot be less than 250ms");
+ }
+
+ this.validationTimeout = validationTimeoutMs;
+ }
+
+ /**
+ * Get the {@link DataSource} that has been explicitly specified to be wrapped by the
+ * pool.
+ *
+ * @return the {@link DataSource} instance, or null
+ */
+ public DataSource getDataSource()
+ {
+ return dataSource;
+ }
+
+ /**
+ * Set a {@link DataSource} for the pool to explicitly wrap. This setter is not
+ * available through property file based initialization.
+ *
+ * @param dataSource a specific {@link DataSource} to be wrapped by the pool
+ */
+ public void setDataSource(DataSource dataSource)
+ {
+ this.dataSource = dataSource;
+ }
+
+ public String getDataSourceClassName()
+ {
+ return dataSourceClassName;
+ }
+
+ public void setDataSourceClassName(String className)
+ {
+ this.dataSourceClassName = className;
+ }
+
+ public void addDataSourceProperty(String propertyName, Object value)
+ {
+ dataSourceProperties.put(propertyName, value);
+ }
+
+ public String getDataSourceJNDI()
+ {
+ return this.dataSourceJndiName;
+ }
+
+ public void setDataSourceJNDI(String jndiDataSource)
+ {
+ this.dataSourceJndiName = jndiDataSource;
+ }
+
+ public Properties getDataSourceProperties()
+ {
+ return dataSourceProperties;
+ }
+
+ public void setDataSourceProperties(Properties dsProperties)
+ {
+ dataSourceProperties.putAll(dsProperties);
+ }
+
+ public String getDriverClassName()
+ {
+ return driverClassName;
+ }
+
+ public void setDriverClassName(String driverClassName)
+ {
+ try {
+ Class<?> driverClass = this.getClass().getClassLoader().loadClass(driverClassName);
+ driverClass.newInstance();
+ this.driverClassName = driverClassName;
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Failed to load class of driverClassName " + driverClassName, e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long getIdleTimeout()
+ {
+ return idleTimeout;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setIdleTimeout(long idleTimeoutMs)
+ {
+ if (idleTimeoutMs < 0) {
+ throw new IllegalArgumentException("idleTimeout cannot be negative");
+ }
+ this.idleTimeout = idleTimeoutMs;
+ }
+
+ public String getJdbcUrl()
+ {
+ return jdbcUrl;
+ }
+
+ public void setJdbcUrl(String jdbcUrl)
+ {
+ this.jdbcUrl = jdbcUrl;
+ }
+
+ /**
+ * Get the default auto-commit behavior of connections in the pool.
+ *
+ * @return the default auto-commit behavior of connections
+ */
+ public boolean isAutoCommit()
+ {
+ return isAutoCommit;
+ }
+
+ /**
+ * Set the default auto-commit behavior of connections in the pool.
+ *
+ * @param isAutoCommit the desired auto-commit default for connections
+ */
+ public void setAutoCommit(boolean isAutoCommit)
+ {
+ this.isAutoCommit = isAutoCommit;
+ }
+
+ /**
+ * Get the pool suspension behavior (allowed or disallowed).
+ *
+ * @return the pool suspension behavior
+ */
+ public boolean isAllowPoolSuspension()
+ {
+ return isAllowPoolSuspension;
+ }
+
+ /**
+ * Set whether or not pool suspension is allowed. There is a performance
+ * impact when pool suspension is enabled. Unless you need it (for a
+ * redundancy system for example) do not enable it.
+ *
+ * @param isAllowPoolSuspension the desired pool suspension allowance
+ */
+ public void setAllowPoolSuspension(boolean isAllowPoolSuspension)
+ {
+ this.isAllowPoolSuspension = isAllowPoolSuspension;
+ }
+
+ /**
+ * Get the pool initialization failure timeout. See {@code #setInitializationFailTimeout(long)}
+ * for details.
+ *
+ * @return the number of milliseconds before the pool initialization fails
+ * @see HikariConfig#setInitializationFailTimeout(long)
+ */
+ public long getInitializationFailTimeout()
+ {
+ return initializationFailTimeout;
+ }
+
+ /**
+ * Set the pool initialization failure timeout. This setting applies to pool
+ * initialization when {@link HikariDataSource} is constructed with a {@link HikariConfig},
+ * or when {@link HikariDataSource} is constructed using the no-arg constructor
+ * and {@link HikariDataSource#getConnection()} is called.
+ * <ul>
+ * <li>Any value of zero or less will <i>not</i> block the calling thread in the
+ * case that a connection cannot be obtained. The pool will start and
+ * continue to try to obtain connections in the background. This can mean
+ * that callers to {@code DataSource#getConnection()} may encounter
+ * exceptions.</li>
+ * <li>Any value greater than zero will be treated as a timeout for pool initialization.
+ * The calling thread will be blocked from continuing until a successful connection
+ * to the database, or until the timeout is reached. If the timeout is reached, then
+ * a {@code PoolInitializationException} will be thrown.
+ * </ul>
+ * Note that this timeout does not override the {@code connectionTimeout} or
+ * {@code validationTimeout}; they will be honored before this timeout is applied. The
+ * default value is one millisecond.
+ *
+ * @param initializationFailTimeout the number of milliseconds before the
+ * pool initialization fails, or 0 or less to skip the initialization
+ * check.
+ */
+ public void setInitializationFailTimeout(long initializationFailTimeout)
+ {
+ this.initializationFailTimeout = initializationFailTimeout;
+ }
+
+ /**
+ * Get whether or not the construction of the pool should throw an exception
+ * if the minimum number of connections cannot be created.
+ *
+ * @return whether or not initialization should fail on error immediately
+ * @deprecated
+ */
+ @Deprecated
+ public boolean isInitializationFailFast()
+ {
+ return initializationFailTimeout > 0;
+ }
+
+ /**
+ * Set whether or not the construction of the pool should throw an exception
+ * if the minimum number of connections cannot be created.
+ *
+ * @param failFast true if the pool should fail if the minimum connections cannot be created
+ * @deprecated
+ */
+ @Deprecated
+ public void setInitializationFailFast(boolean failFast)
+ {
+ LOGGER.warn("The initializationFailFast propery is deprecated, see initializationFailTimeout");
+
+ initializationFailTimeout = (failFast ? 1 : 0);
+ }
+
+ public boolean isIsolateInternalQueries()
+ {
+ return isIsolateInternalQueries;
+ }
+
+ public void setIsolateInternalQueries(boolean isolate)
+ {
+ this.isIsolateInternalQueries = isolate;
+ }
+
+ @Deprecated
+ public boolean isJdbc4ConnectionTest()
+ {
+ return false;
+ }
+
+ @Deprecated
+ public void setJdbc4ConnectionTest(boolean useIsValid)
+ {
+ LOGGER.warn("The jdbcConnectionTest property is now deprecated, see the documentation for connectionTestQuery");
+ }
+
+ public MetricsTrackerFactory getMetricsTrackerFactory()
+ {
+ return metricsTrackerFactory;
+ }
+
+ public void setMetricsTrackerFactory(MetricsTrackerFactory metricsTrackerFactory)
+ {
+ if (metricRegistry != null) {
+ throw new IllegalStateException("cannot use setMetricsTrackerFactory() and setMetricRegistry() together");
+ }
+
+ this.metricsTrackerFactory = metricsTrackerFactory;
+ }
+
+ /**
+ * Get the Codahale MetricRegistry, could be null.
+ *
+ * @return the codahale MetricRegistry instance
+ */
+ public Object getMetricRegistry()
+ {
+ return metricRegistry;
+ }
+
+ /**
+ * Set a Codahale MetricRegistry to use for HikariCP.
+ *
+ * @param metricRegistry the Codahale MetricRegistry to set
+ */
+ public void setMetricRegistry(Object metricRegistry)
+ {
+ if (metricsTrackerFactory != null) {
+ throw new IllegalStateException("cannot use setMetricRegistry() and setMetricsTrackerFactory() together");
+ }
+
+ if (metricRegistry != null) {
+ if (metricRegistry instanceof String) {
+ try {
+ InitialContext initCtx = new InitialContext();
+ metricRegistry = initCtx.lookup((String) metricRegistry);
+ }
+ catch (NamingException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ if (!(metricRegistry instanceof MetricRegistry)) {
+ throw new IllegalArgumentException("Class must be an instance of com.codahale.metrics.MetricRegistry");
+ }
+ }
+
+ this.metricRegistry = metricRegistry;
+ }
+
+ /**
+ * Get the Codahale HealthCheckRegistry, could be null.
+ *
+ * @return the Codahale HealthCheckRegistry instance
+ */
+ public Object getHealthCheckRegistry()
+ {
+ return healthCheckRegistry;
+ }
+
+ /**
+ * Set a Codahale HealthCheckRegistry to use for HikariCP.
+ *
+ * @param healthCheckRegistry the Codahale HealthCheckRegistry to set
+ */
+ public void setHealthCheckRegistry(Object healthCheckRegistry)
+ {
+ if (healthCheckRegistry != null) {
+ if (healthCheckRegistry instanceof String) {
+ try {
+ InitialContext initCtx = new InitialContext();
+ healthCheckRegistry = initCtx.lookup((String) healthCheckRegistry);
+ }
+ catch (NamingException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ if (!(healthCheckRegistry instanceof HealthCheckRegistry)) {
+ throw new IllegalArgumentException("Class must be an instance of com.codahale.metrics.health.HealthCheckRegistry");
+ }
+ }
+
+ this.healthCheckRegistry = healthCheckRegistry;
+ }
+
+ public Properties getHealthCheckProperties()
+ {
+ return healthCheckProperties;
+ }
+
+ public void setHealthCheckProperties(Properties healthCheckProperties)
+ {
+ this.healthCheckProperties.putAll(healthCheckProperties);
+ }
+
+ public void addHealthCheckProperty(String key, String value)
+ {
+ healthCheckProperties.setProperty(key, value);
+ }
+
+ public boolean isReadOnly()
+ {
+ return isReadOnly;
+ }
+
+ public void setReadOnly(boolean readOnly)
+ {
+ this.isReadOnly = readOnly;
+ }
+
+ public boolean isRegisterMbeans()
+ {
+ return isRegisterMbeans;
+ }
+
+ public void setRegisterMbeans(boolean register)
+ {
+ this.isRegisterMbeans = register;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long getLeakDetectionThreshold()
+ {
+ return leakDetectionThreshold;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setLeakDetectionThreshold(long leakDetectionThresholdMs)
+ {
+ this.leakDetectionThreshold = leakDetectionThresholdMs;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long getMaxLifetime()
+ {
+ return maxLifetime;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setMaxLifetime(long maxLifetimeMs)
+ {
+ this.maxLifetime = maxLifetimeMs;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getMaximumPoolSize()
+ {
+ return maxPoolSize;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setMaximumPoolSize(int maxPoolSize)
+ {
+ if (maxPoolSize < 1) {
+ throw new IllegalArgumentException("maxPoolSize cannot be less than 1");
+ }
+ this.maxPoolSize = maxPoolSize;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getMinimumIdle()
+ {
+ return minIdle;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setMinimumIdle(int minIdle)
+ {
+ if (minIdle < 0) {
+ throw new IllegalArgumentException("minimumIdle cannot be negative");
+ }
+ this.minIdle = minIdle;
+ }
+
+ /**
+ * Get the default password to use for DataSource.getConnection(username, password) calls.
+ * @return the password
+ */
+ public String getPassword()
+ {
+ return password;
+ }
+
+ /**
+ * Set the default password to use for DataSource.getConnection(username, password) calls.
+ * @param password the password
+ */
+ @Override
+ public void setPassword(String password)
+ {
+ this.password = password;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getPoolName()
+ {
+ return poolName;
+ }
+
+ /**
+ * Set the name of the connection pool. This is primarily used for the MBean
+ * to uniquely identify the pool configuration.
+ *
+ * @param poolName the name of the connection pool to use
+ */
+ public void setPoolName(String poolName)
+ {
+ this.poolName = poolName;
+ }
+
+ /**
+ * Get the ScheduledExecutorService used for housekeeping.
+ *
+ * @return the executor
+ */
+ @Deprecated
+ public ScheduledThreadPoolExecutor getScheduledExecutorService()
+ {
+ return (ScheduledThreadPoolExecutor) scheduledExecutor;
+ }
+
+ /**
+ * Set the ScheduledExecutorService used for housekeeping.
+ *
+ * @param executor the ScheduledExecutorService
+ */
+ @Deprecated
+ public void setScheduledExecutorService(ScheduledThreadPoolExecutor executor)
+ {
+ this.scheduledExecutor = executor;
+ }
+
+ /**
+ * Get the ScheduledExecutorService used for housekeeping.
+ *
+ * @return the executor
+ */
+ public ScheduledExecutorService getScheduledExecutor()
+ {
+ return scheduledExecutor;
+ }
+
+ /**
+ * Set the ScheduledExecutorService used for housekeeping.
+ *
+ * @param executor the ScheduledExecutorService
+ */
+ public void setScheduledExecutor(ScheduledExecutorService executor)
+ {
+ this.scheduledExecutor = executor;
+ }
+
+ public String getTransactionIsolation()
+ {
+ return transactionIsolationName;
+ }
+
+ /**
+ * Set the default transaction isolation level. The specified value is the
+ * constant name from the <code>Connection</code> class, eg.
+ * <code>TRANSACTION_REPEATABLE_READ</code>.
+ *
+ * @param isolationLevel the name of the isolation level
+ */
+ public void setTransactionIsolation(String isolationLevel)
+ {
+ this.transactionIsolationName = isolationLevel;
+ }
+
+ /**
+ * Get the default username used for DataSource.getConnection(username, password) calls.
+ *
+ * @return the username
+ */
+ public String getUsername()
+ {
+ return username;
+ }
+
+ /**
+ * Set the default username used for DataSource.getConnection(username, password) calls.
+ *
+ * @param username the username
+ */
+ @Override
+ public void setUsername(String username)
+ {
+ this.username = username;
+ }
+
+ /**
+ * Get the thread factory used to create threads.
+ *
+ * @return the thread factory (may be null, in which case the default thread factory is used)
+ */
+ public ThreadFactory getThreadFactory()
+ {
+ return threadFactory;
+ }
+
+ /**
+ * Set the thread factory to be used to create threads.
+ *
+ * @param threadFactory the thread factory (setting to null causes the default thread factory to be used)
+ */
+ public void setThreadFactory(ThreadFactory threadFactory)
+ {
+ this.threadFactory = threadFactory;
+ }
+
+ public void validate()
+ {
+ if (poolName == null) {
+ poolName = "HikariPool-" + generatePoolNumber();
+ }
+ else if (isRegisterMbeans && poolName.contains(":")) {
+ throw new IllegalArgumentException("poolName cannot contain ':' when used with JMX");
+ }
+
+ // treat empty property as null
+ catalog = getNullIfEmpty(catalog);
+ connectionInitSql = getNullIfEmpty(connectionInitSql);
+ connectionTestQuery = getNullIfEmpty(connectionTestQuery);
+ transactionIsolationName = getNullIfEmpty(transactionIsolationName);
+ dataSourceClassName = getNullIfEmpty(dataSourceClassName);
+ dataSourceJndiName = getNullIfEmpty(dataSourceJndiName);
+ driverClassName = getNullIfEmpty(driverClassName);
+ jdbcUrl = getNullIfEmpty(jdbcUrl);
+
+ // Check Data Source Options
+ if (dataSource != null) {
+ if (dataSourceClassName != null) {
+ LOGGER.warn("{} - using dataSource and ignoring dataSourceClassName.", poolName);
+ }
+ }
+ else if (dataSourceClassName != null) {
+ if (driverClassName != null) {
+ LOGGER.error("{} - cannot use driverClassName and dataSourceClassName together.", poolName);
+ // NOTE: This exception text is referenced by a Spring Boot FailureAnalyzer, it should not be
+ // changed without first notifying the Spring Boot developers.
+ throw new IllegalStateException("cannot use driverClassName and dataSourceClassName together.");
+ }
+ else if (jdbcUrl != null) {
+ LOGGER.warn("{} - using dataSourceClassName and ignoring jdbcUrl.", poolName);
+ }
+ }
+ else if (jdbcUrl != null) {
+ }
+ else if (driverClassName != null) {
+ LOGGER.error("{} - jdbcUrl is required with driverClassName.", poolName);
+ throw new IllegalArgumentException("jdbcUrl is required with driverClassName.");
+ }
+ else {
+ LOGGER.error("{} - dataSource or dataSourceClassName or jdbcUrl is required.", poolName);
+ throw new IllegalArgumentException("dataSource or dataSourceClassName or jdbcUrl is required.");
+ }
+
+ validateNumerics();
+
+ if (LOGGER.isDebugEnabled() || unitTest) {
+ logConfiguration();
+ }
+ }
+
+ private void validateNumerics()
+ {
+ if (maxLifetime != 0 && maxLifetime < SECONDS.toMillis(30)) {
+ LOGGER.warn("{} - maxLifetime is less than 30000ms, setting to default {}ms.", poolName, MAX_LIFETIME);
+ maxLifetime = MAX_LIFETIME;
+ }
+
+ if (idleTimeout + SECONDS.toMillis(1) > maxLifetime && maxLifetime > 0) {
+ LOGGER.warn("{} - idleTimeout is close to or more than maxLifetime, disabling it.", poolName);
+ idleTimeout = 0;
+ }
+
+ if (idleTimeout != 0 && idleTimeout < SECONDS.toMillis(10)) {
+ LOGGER.warn("{} - idleTimeout is less than 10000ms, setting to default {}ms.", poolName, IDLE_TIMEOUT);
+ idleTimeout = IDLE_TIMEOUT;
+ }
+
+ if (leakDetectionThreshold > 0 && !unitTest) {
+ if (leakDetectionThreshold < SECONDS.toMillis(2) || (leakDetectionThreshold > maxLifetime && maxLifetime > 0)) {
+ LOGGER.warn("{} - leakDetectionThreshold is less than 2000ms or more than maxLifetime, disabling it.", poolName);
+ leakDetectionThreshold = 0;
+ }
+ }
+
+ if (connectionTimeout < 250) {
+ LOGGER.warn("{} - connectionTimeout is less than 250ms, setting to {}ms.", poolName, CONNECTION_TIMEOUT);
+ connectionTimeout = CONNECTION_TIMEOUT;
+ }
+
+ if (validationTimeout < 250) {
+ LOGGER.warn("{} - validationTimeout is less than 250ms, setting to {}ms.", poolName, VALIDATION_TIMEOUT);
+ validationTimeout = VALIDATION_TIMEOUT;
+ }
+
+ if (maxPoolSize < 1) {
+ maxPoolSize = (minIdle <= 0) ? DEFAULT_POOL_SIZE : minIdle;
+ }
+
+ if (minIdle < 0 || minIdle > maxPoolSize) {
+ minIdle = maxPoolSize;
+ }
+ }
+
+ private void logConfiguration()
+ {
+ LOGGER.debug("{} - configuration:", poolName);
+ final Set<String> propertyNames = new TreeSet<>(PropertyElf.getPropertyNames(HikariConfig.class));
+ for (String prop : propertyNames) {
+ try {
+ Object value = PropertyElf.getProperty(prop, this);
+ if ("dataSourceProperties".equals(prop)) {
+ Properties dsProps = PropertyElf.copyProperties(dataSourceProperties);
+ dsProps.setProperty("password", "<masked>");
+ value = dsProps;
+ }
+
+ if ("initializationFailTimeout".equals(prop) && initializationFailTimeout == Long.MAX_VALUE) {
+ value = "infinite";
+ }
+ else if ("transactionIsolation".equals(prop) && transactionIsolationName == null) {
+ value = "default";
+ }
+ else if (prop.matches("scheduledExecutorService|threadFactory") && value == null) {
+ value = "internal";
+ }
+ else if (prop.contains("password")) {
+ value = "<masked>";
+ }
+ else if (value instanceof String) {
+ value = "\"" + value + "\""; // quote to see lead/trailing spaces is any
+ }
+ else if (value == null) {
+ value = "none";
+ }
+ LOGGER.debug((prop + "................................................").substring(0, 32) + value);
+ }
+ catch (Exception e) {
+ continue;
+ }
+ }
+ }
+
+ protected void loadProperties(String propertyFileName)
+ {
+ final File propFile = new File(propertyFileName);
+ try (final InputStream is = propFile.isFile() ? new FileInputStream(propFile) : this.getClass().getResourceAsStream(propertyFileName)) {
+ if (is != null) {
+ Properties props = new Properties();
+ props.load(is);
+ PropertyElf.setTargetFromProperties(this, props);
+ }
+ else {
+ throw new IllegalArgumentException("Cannot find property file: " + propertyFileName);
+ }
+ }
+ catch (IOException io) {
+ throw new RuntimeException("Failed to read property file", io);
+ }
+ }
+
+ private int generatePoolNumber()
+ {
+ // Pool number is global to the VM to avoid overlapping pool numbers in classloader scoped environments
+ synchronized (System.getProperties()) {
+ final int next = Integer.getInteger("com.zaxxer.hikari.pool_number", 0) + 1;
+ System.setProperty("com.zaxxer.hikari.pool_number", String.valueOf(next));
+ return next;
+ }
+ }
+
+ public void copyState(HikariConfig other)
+ {
+ for (Field field : HikariConfig.class.getDeclaredFields()) {
+ if (!Modifier.isFinal(field.getModifiers())) {
+ field.setAccessible(true);
+ try {
+ field.set(other, field.get(this));
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Failed to copy HikariConfig state: " + e.getMessage(), e);
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/HikariConfigMXBean.java b/src/main/java/com/zaxxer/hikari/HikariConfigMXBean.java
new file mode 100644
index 0000000..00fefc9
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/HikariConfigMXBean.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari;
+
+/**
+ * The javax.management MBean for a Hikari pool configuration.
+ *
+ * @author Brett Wooldridge
+ */
+public interface HikariConfigMXBean
+{
+ /**
+ * Get the maximum number of milliseconds that a client will wait for a connection from the pool. If this
+ * time is exceeded without a connection becoming available, a SQLException will be thrown from
+ * {@link javax.sql.DataSource#getConnection()}.
+ *
+ * @return the connection timeout in milliseconds
+ */
+ long getConnectionTimeout();
+
+ /**
+ * Set the maximum number of milliseconds that a client will wait for a connection from the pool. If this
+ * time is exceeded without a connection becoming available, a SQLException will be thrown from
+ * {@link javax.sql.DataSource#getConnection()}.
+ *
+ * @param connectionTimeoutMs the connection timeout in milliseconds
+ */
+ void setConnectionTimeout(long connectionTimeoutMs);
+
+ /**
+ * Get the maximum number of milliseconds that the pool will wait for a connection to be validated as
+ * alive.
+ *
+ * @return the validation timeout in milliseconds
+ */
+ long getValidationTimeout();
+
+ /**
+ * Sets the maximum number of milliseconds that the pool will wait for a connection to be validated as
+ * alive.
+ *
+ * @param validationTimeoutMs the validation timeout in milliseconds
+ */
+ void setValidationTimeout(long validationTimeoutMs);
+
+ /**
+ * This property controls the maximum amount of time (in milliseconds) that a connection is allowed to sit
+ * idle in the pool. Whether a connection is retired as idle or not is subject to a maximum variation of +30
+ * seconds, and average variation of +15 seconds. A connection will never be retired as idle before this timeout.
+ * A value of 0 means that idle connections are never removed from the pool.
+ *
+ * @return the idle timeout in milliseconds
+ */
+ long getIdleTimeout();
+
+ /**
+ * This property controls the maximum amount of time (in milliseconds) that a connection is allowed to sit
+ * idle in the pool. Whether a connection is retired as idle or not is subject to a maximum variation of +30
+ * seconds, and average variation of +15 seconds. A connection will never be retired as idle before this timeout.
+ * A value of 0 means that idle connections are never removed from the pool.
+ *
+ * @param idleTimeoutMs the idle timeout in milliseconds
+ */
+ void setIdleTimeout(long idleTimeoutMs);
+
+ /**
+ * This property controls the amount of time that a connection can be out of the pool before a message is
+ * logged indicating a possible connection leak. A value of 0 means leak detection is disabled.
+ *
+ * @return the connection leak detection threshold in milliseconds
+ */
+ long getLeakDetectionThreshold();
+
+ /**
+ * This property controls the amount of time that a connection can be out of the pool before a message is
+ * logged indicating a possible connection leak. A value of 0 means leak detection is disabled.
+ *
+ * @param leakDetectionThresholdMs the connection leak detection threshold in milliseconds
+ */
+ void setLeakDetectionThreshold(long leakDetectionThresholdMs);
+
+ /**
+ * This property controls the maximum lifetime of a connection in the pool. When a connection reaches this
+ * timeout, even if recently used, it will be retired from the pool. An in-use connection will never be
+ * retired, only when it is idle will it be removed.
+ *
+ * @return the maximum connection lifetime in milliseconds
+ */
+ long getMaxLifetime();
+
+ /**
+ * This property controls the maximum lifetime of a connection in the pool. When a connection reaches this
+ * timeout, even if recently used, it will be retired from the pool. An in-use connection will never be
+ * retired, only when it is idle will it be removed.
+ *
+ * @param maxLifetimeMs the maximum connection lifetime in milliseconds
+ */
+ void setMaxLifetime(long maxLifetimeMs);
+
+ /**
+ * The property controls the maximum size that the pool is allowed to reach, including both idle and in-use
+ * connections. Basically this value will determine the maximum number of actual connections to the database
+ * backend.
+ * <p>
+ * When the pool reaches this size, and no idle connections are available, calls to getConnection() will
+ * block for up to connectionTimeout milliseconds before timing out.
+ *
+ * @return the minimum number of connections in the pool
+ */
+ int getMinimumIdle();
+
+ /**
+ * The property controls the minimum number of idle connections that HikariCP tries to maintain in the pool,
+ * including both idle and in-use connections. If the idle connections dip below this value, HikariCP will
+ * make a best effort to restore them quickly and efficiently.
+ *
+ * @param minIdle the minimum number of idle connections in the pool to maintain
+ */
+ void setMinimumIdle(int minIdle);
+
+ /**
+ * The property controls the maximum number of connections that HikariCP will keep in the pool,
+ * including both idle and in-use connections.
+ *
+ * @return the maximum number of connections in the pool
+ */
+ int getMaximumPoolSize();
+
+ /**
+ * The property controls the maximum size that the pool is allowed to reach, including both idle and in-use
+ * connections. Basically this value will determine the maximum number of actual connections to the database
+ * backend.
+ * <p>
+ * When the pool reaches this size, and no idle connections are available, calls to getConnection() will
+ * block for up to connectionTimeout milliseconds before timing out.
+ *
+ * @param maxPoolSize the maximum number of connections in the pool
+ */
+ void setMaximumPoolSize(int maxPoolSize);
+
+ /**
+ * Set the password used for authentication. Changing this at runtime will apply to new connections only.
+ * Altering this at runtime only works for DataSource-based connections, not Driver-class or JDBC URL-based
+ * connections.
+ *
+ * @param password the database password
+ */
+ void setPassword(String password);
+
+ /**
+ * Set the username used for authentication. Changing this at runtime will apply to new connections only.
+ * Altering this at runtime only works for DataSource-based connections, not Driver-class or JDBC URL-based
+ * connections.
+ *
+ * @param username the database username
+ */
+ void setUsername(String username);
+
+
+ /**
+ * The name of the connection pool.
+ *
+ * @return the name of the connection pool
+ */
+ String getPoolName();
+} \ No newline at end of file
diff --git a/src/main/java/com/zaxxer/hikari/HikariDataSource.java b/src/main/java/com/zaxxer/hikari/HikariDataSource.java
new file mode 100644
index 0000000..29b8953
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/HikariDataSource.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari;
+
+import java.io.Closeable;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.sql.DataSource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
+import com.zaxxer.hikari.pool.HikariPool;
+import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException;
+
+/**
+ * The HikariCP pooled DataSource.
+ *
+ * @author Brett Wooldridge
+ */
+public class HikariDataSource extends HikariConfig implements DataSource, Closeable
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(HikariDataSource.class);
+
+ private final AtomicBoolean isShutdown = new AtomicBoolean();
+
+ private final HikariPool fastPathPool;
+ private volatile HikariPool pool;
+
+ /**
+ * Default constructor. Setters be used to configure the pool. Using
+ * this constructor vs. {@link #HikariDataSource(HikariConfig)} will
+ * result in {@link #getConnection()} performance that is slightly lower
+ * due to lazy initialization checks.
+ */
+ public HikariDataSource()
+ {
+ super();
+ fastPathPool = null;
+ }
+
+ /**
+ * Construct a HikariDataSource with the specified configuration.
+ *
+ * @param configuration a HikariConfig instance
+ */
+ public HikariDataSource(HikariConfig configuration)
+ {
+ configuration.validate();
+ configuration.copyState(this);
+
+ LOGGER.info("{} - Starting...", configuration.getPoolName());
+ pool = fastPathPool = new HikariPool(this);
+ LOGGER.info("{} - Start completed.", configuration.getPoolName());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Connection getConnection() throws SQLException
+ {
+ if (isClosed()) {
+ throw new SQLException("HikariDataSource " + this + " has been closed.");
+ }
+
+ if (fastPathPool != null) {
+ return fastPathPool.getConnection();
+ }
+
+ // See http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java
+ HikariPool result = pool;
+ if (result == null) {
+ synchronized (this) {
+ result = pool;
+ if (result == null) {
+ validate();
+ LOGGER.info("{} - Starting...", getPoolName());
+ try {
+ pool = result = new HikariPool(this);
+ }
+ catch (PoolInitializationException pie) {
+ if (pie.getCause() instanceof SQLException) {
+ throw (SQLException) pie.getCause();
+ }
+ else {
+ throw pie;
+ }
+ }
+ LOGGER.info("{} - Start completed.", getPoolName());
+ }
+ }
+ }
+
+ return result.getConnection();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Connection getConnection(String username, String password) throws SQLException
+ {
+ throw new SQLFeatureNotSupportedException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public PrintWriter getLogWriter() throws SQLException
+ {
+ HikariPool p = pool;
+ return (p != null ? p.getUnwrappedDataSource().getLogWriter() : null);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setLogWriter(PrintWriter out) throws SQLException
+ {
+ HikariPool p = pool;
+ if (p != null) {
+ p.getUnwrappedDataSource().setLogWriter(out);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setLoginTimeout(int seconds) throws SQLException
+ {
+ HikariPool p = pool;
+ if (p != null) {
+ p.getUnwrappedDataSource().setLoginTimeout(seconds);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getLoginTimeout() throws SQLException
+ {
+ HikariPool p = pool;
+ return (p != null ? p.getUnwrappedDataSource().getLoginTimeout() : 0);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException
+ {
+ throw new SQLFeatureNotSupportedException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T unwrap(Class<T> iface) throws SQLException
+ {
+ if (iface.isInstance(this)) {
+ return (T) this;
+ }
+
+ HikariPool p = pool;
+ if (p != null) {
+ final DataSource unwrappedDataSource = p.getUnwrappedDataSource();
+ if (iface.isInstance(unwrappedDataSource)) {
+ return (T) unwrappedDataSource;
+ }
+
+ if (unwrappedDataSource != null) {
+ return unwrappedDataSource.unwrap(iface);
+ }
+ }
+
+ throw new SQLException("Wrapped DataSource is not an instance of " + iface);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isWrapperFor(Class<?> iface) throws SQLException
+ {
+ if (iface.isInstance(this)) {
+ return true;
+ }
+
+ HikariPool p = pool;
+ if (p != null) {
+ final DataSource unwrappedDataSource = p.getUnwrappedDataSource();
+ if (iface.isInstance(unwrappedDataSource)) {
+ return true;
+ }
+
+ if (unwrappedDataSource != null) {
+ return unwrappedDataSource.isWrapperFor(iface);
+ }
+ }
+
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setMetricRegistry(Object metricRegistry)
+ {
+ boolean isAlreadySet = getMetricRegistry() != null;
+ super.setMetricRegistry(metricRegistry);
+
+ HikariPool p = pool;
+ if (p != null) {
+ if (isAlreadySet) {
+ throw new IllegalStateException("MetricRegistry can only be set one time");
+ }
+ else {
+ p.setMetricRegistry(super.getMetricRegistry());
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setMetricsTrackerFactory(MetricsTrackerFactory metricsTrackerFactory)
+ {
+ boolean isAlreadySet = getMetricsTrackerFactory() != null;
+ super.setMetricsTrackerFactory(metricsTrackerFactory);
+
+ HikariPool p = pool;
+ if (p != null) {
+ if (isAlreadySet) {
+ throw new IllegalStateException("MetricsTrackerFactory can only be set one time");
+ }
+ else {
+ p.setMetricsTrackerFactory(super.getMetricsTrackerFactory());
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setHealthCheckRegistry(Object healthCheckRegistry)
+ {
+ boolean isAlreadySet = getHealthCheckRegistry() != null;
+ super.setHealthCheckRegistry(healthCheckRegistry);
+
+ HikariPool p = pool;
+ if (p != null) {
+ if (isAlreadySet) {
+ throw new IllegalStateException("HealthCheckRegistry can only be set one time");
+ }
+ else {
+ p.setHealthCheckRegistry(super.getHealthCheckRegistry());
+ }
+ }
+ }
+
+ /**
+ * Evict a connection from the pool. If the connection has already been closed (returned to the pool)
+ * this may result in a "soft" eviction; the connection will be evicted sometime in the future if it is
+ * currently in use. If the connection has not been closed, the eviction is immediate.
+ *
+ * @param connection the connection to evict from the pool
+ */
+ public void evictConnection(Connection connection)
+ {
+ HikariPool p;
+ if (!isClosed() && (p = pool) != null && connection.getClass().getName().startsWith("com.zaxxer.hikari")) {
+ p.evictConnection(connection);
+ }
+ }
+
+ /**
+ * Suspend allocation of connections from the pool. All callers to <code>getConnection()</code>
+ * will block indefinitely until <code>resumePool()</code> is called.
+ */
+ public void suspendPool()
+ {
+ HikariPool p;
+ if (!isClosed() && (p = pool) != null) {
+ p.suspendPool();
+ }
+ }
+
+ /**
+ * Resume allocation of connections from the pool.
+ */
+ public void resumePool()
+ {
+ HikariPool p;
+ if (!isClosed() && (p = pool) != null) {
+ p.resumePool();
+ }
+ }
+
+ /**
+ * Shutdown the DataSource and its associated pool.
+ */
+ @Override
+ public void close()
+ {
+ if (isShutdown.getAndSet(true)) {
+ return;
+ }
+
+ HikariPool p = pool;
+ if (p != null) {
+ try {
+ LOGGER.info("{} - Shutdown initiated...", getPoolName());
+ p.shutdown();
+ LOGGER.info("{} - Shutdown completed.", getPoolName());
+ }
+ catch (InterruptedException e) {
+ LOGGER.warn("{} - Interrupted during closing", getPoolName(), e);
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ /**
+ * Determine whether the HikariDataSource has been closed.
+ *
+ * @return true if the HikariDataSource has been closed, false otherwise
+ */
+ public boolean isClosed()
+ {
+ return isShutdown.get();
+ }
+
+ /**
+ * Shutdown the DataSource and its associated pool.
+ *
+ * @deprecated This method has been deprecated, please use {@link #close()} instead
+ */
+ @Deprecated
+ public void shutdown()
+ {
+ LOGGER.warn("The shutdown() method has been deprecated, please use the close() method instead");
+ close();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString()
+ {
+ return "HikariDataSource (" + pool + ")";
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/HikariJNDIFactory.java b/src/main/java/com/zaxxer/hikari/HikariJNDIFactory.java
new file mode 100644
index 0000000..5136028
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/HikariJNDIFactory.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2013,2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.naming.spi.ObjectFactory;
+import javax.sql.DataSource;
+
+import com.zaxxer.hikari.util.PropertyElf;
+
+/**
+ * A JNDI factory that produces HikariDataSource instances.
+ *
+ * @author Brett Wooldridge
+ */
+public class HikariJNDIFactory implements ObjectFactory
+{
+ @Override
+ synchronized public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception
+ {
+ // We only know how to deal with <code>javax.naming.Reference</code> that specify a class name of "javax.sql.DataSource"
+ if (!(obj instanceof Reference)) {
+ return null;
+ }
+
+ Reference ref = (Reference) obj;
+ if (!"javax.sql.DataSource".equals(ref.getClassName())) {
+ throw new NamingException(ref.getClassName() + " is not a valid class name/type for this JNDI factory.");
+ }
+
+ Set<String> hikariPropSet = PropertyElf.getPropertyNames(HikariConfig.class);
+
+ Properties properties = new Properties();
+ Enumeration<RefAddr> enumeration = ref.getAll();
+ while (enumeration.hasMoreElements()) {
+ RefAddr element = enumeration.nextElement();
+ String type = element.getType();
+ if (type.startsWith("dataSource.") || hikariPropSet.contains(type)) {
+ properties.setProperty(type, element.getContent().toString());
+ }
+ }
+
+ return createDataSource(properties, nameCtx);
+ }
+
+ private DataSource createDataSource(final Properties properties, final Context context) throws NamingException
+ {
+ String jndiName = properties.getProperty("dataSourceJNDI");
+ if (jndiName != null) {
+ return lookupJndiDataSource(properties, context, jndiName);
+ }
+
+ return new HikariDataSource(new HikariConfig(properties));
+ }
+
+ private DataSource lookupJndiDataSource(final Properties properties, final Context context, final String jndiName) throws NamingException
+ {
+ if (context == null) {
+ throw new RuntimeException("JNDI context does not found for dataSourceJNDI : " + jndiName);
+ }
+
+ DataSource jndiDS = (DataSource) context.lookup(jndiName);
+ if (jndiDS == null) {
+ final Context ic = new InitialContext();
+ jndiDS = (DataSource) ic.lookup(jndiName);
+ ic.close();
+ }
+
+ if (jndiDS != null) {
+ HikariConfig config = new HikariConfig(properties);
+ config.setDataSource(jndiDS);
+ return new HikariDataSource(config);
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/HikariPoolMXBean.java b/src/main/java/com/zaxxer/hikari/HikariPoolMXBean.java
new file mode 100644
index 0000000..22c3dbc
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/HikariPoolMXBean.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari;
+
+/**
+ * The javax.management MBean for a Hikari pool instance.
+ *
+ * @author Brett Wooldridge
+ */
+public interface HikariPoolMXBean
+{
+ int getIdleConnections();
+
+ int getActiveConnections();
+
+ int getTotalConnections();
+
+ int getThreadsAwaitingConnection();
+
+ void softEvictConnections();
+
+ void suspendPool();
+
+ void resumePool();
+}
diff --git a/src/main/java/com/zaxxer/hikari/hibernate/HikariConfigurationUtil.java b/src/main/java/com/zaxxer/hikari/hibernate/HikariConfigurationUtil.java
new file mode 100644
index 0000000..7b58e2d
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/hibernate/HikariConfigurationUtil.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.hibernate;
+
+import java.util.Map;
+import java.util.Properties;
+
+import org.hibernate.cfg.AvailableSettings;
+
+import com.zaxxer.hikari.HikariConfig;
+
+/**
+ * Utility class to map Hibernate properties to HikariCP configuration properties.
+ *
+ * @author Brett Wooldridge, Luca Burgazzoli
+ */
+public class HikariConfigurationUtil
+{
+ public static final String CONFIG_PREFIX = "hibernate.hikari.";
+ public static final String CONFIG_PREFIX_DATASOURCE = "hibernate.hikari.dataSource.";
+
+ /**
+ * Create/load a HikariConfig from Hibernate properties.
+ *
+ * @param props a map of Hibernate properties
+ * @return a HikariConfig
+ */
+ @SuppressWarnings("rawtypes")
+ public static HikariConfig loadConfiguration(Map props)
+ {
+ Properties hikariProps = new Properties();
+ copyProperty(AvailableSettings.ISOLATION, props, "transactionIsolation", hikariProps);
+ copyProperty(AvailableSettings.AUTOCOMMIT, props, "autoCommit", hikariProps);
+ copyProperty(AvailableSettings.DRIVER, props, "driverClassName", hikariProps);
+ copyProperty(AvailableSettings.URL, props, "jdbcUrl", hikariProps);
+ copyProperty(AvailableSettings.USER, props, "username", hikariProps);
+ copyProperty(AvailableSettings.PASS, props, "password", hikariProps);
+
+ for (Object keyo : props.keySet()) {
+ String key = (String) keyo;
+ if (key.startsWith(CONFIG_PREFIX)) {
+ hikariProps.setProperty(key.substring(CONFIG_PREFIX.length()), (String) props.get(key));
+ }
+ }
+
+ return new HikariConfig(hikariProps);
+ }
+
+ @SuppressWarnings("rawtypes")
+ private static void copyProperty(String srcKey, Map src, String dstKey, Properties dst)
+ {
+ if (src.containsKey(srcKey)) {
+ dst.setProperty(dstKey, (String) src.get(srcKey));
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/zaxxer/hikari/hibernate/HikariConnectionProvider.java b/src/main/java/com/zaxxer/hikari/hibernate/HikariConnectionProvider.java
new file mode 100644
index 0000000..4f9656c
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/hibernate/HikariConnectionProvider.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.hibernate;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Map;
+
+import org.hibernate.HibernateException;
+import org.hibernate.Version;
+import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
+import org.hibernate.service.UnknownUnwrapTypeException;
+import org.hibernate.service.spi.Configurable;
+import org.hibernate.service.spi.Stoppable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+import javax.sql.DataSource;
+
+/**
+ * Connection provider for Hibernate 4.3.
+ *
+ * @author Brett Wooldridge, Luca Burgazzoli
+ */
+public class HikariConnectionProvider implements ConnectionProvider, Configurable, Stoppable
+{
+ private static final long serialVersionUID = -9131625057941275711L;
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(HikariConnectionProvider.class);
+
+ /**
+ * HikariCP configuration.
+ */
+ private HikariConfig hcfg;
+
+ /**
+ * HikariCP data source.
+ */
+ private HikariDataSource hds;
+
+ // *************************************************************************
+ //
+ // *************************************************************************
+
+ /**
+ * c-tor
+ */
+ public HikariConnectionProvider()
+ {
+ this.hcfg = null;
+ this.hds = null;
+ if (Version.getVersionString().substring(0, 5).compareTo("4.3.6") >= 1) {
+ LOGGER.warn("com.zaxxer.hikari.hibernate.HikariConnectionProvider has been deprecated for versions of "
+ + "Hibernate 4.3.6 and newer. Please switch to org.hibernate.hikaricp.internal.HikariCPConnectionProvider.");
+ }
+ }
+
+ // *************************************************************************
+ // Configurable
+ // *************************************************************************
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public void configure(Map props) throws HibernateException
+ {
+ try {
+ LOGGER.debug("Configuring HikariCP");
+
+ this.hcfg = HikariConfigurationUtil.loadConfiguration(props);
+ this.hds = new HikariDataSource(this.hcfg);
+
+ }
+ catch (Exception e) {
+ throw new HibernateException(e);
+ }
+
+ LOGGER.debug("HikariCP Configured");
+ }
+
+ // *************************************************************************
+ // ConnectionProvider
+ // *************************************************************************
+
+ @Override
+ public Connection getConnection() throws SQLException
+ {
+ Connection conn = null;
+ if (this.hds != null) {
+ conn = this.hds.getConnection();
+ }
+
+ return conn;
+ }
+
+ @Override
+ public void closeConnection(Connection conn) throws SQLException
+ {
+ conn.close();
+ }
+
+ @Override
+ public boolean supportsAggressiveRelease()
+ {
+ return false;
+ }
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ public boolean isUnwrappableAs(Class unwrapType)
+ {
+ return ConnectionProvider.class.equals(unwrapType) || HikariConnectionProvider.class.isAssignableFrom(unwrapType);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T unwrap(Class<T> unwrapType)
+ {
+ if ( ConnectionProvider.class.equals( unwrapType ) ||
+ HikariConnectionProvider.class.isAssignableFrom( unwrapType ) ) {
+ return (T) this;
+ }
+ else if ( DataSource.class.isAssignableFrom( unwrapType ) ) {
+ return (T) this.hds;
+ }
+ else {
+ throw new UnknownUnwrapTypeException( unwrapType );
+ }
+ }
+
+ // *************************************************************************
+ // Stoppable
+ // *************************************************************************
+
+ @Override
+ public void stop()
+ {
+ this.hds.close();
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/metrics/IMetricsTracker.java b/src/main/java/com/zaxxer/hikari/metrics/IMetricsTracker.java
new file mode 100644
index 0000000..18ae9a5
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/metrics/IMetricsTracker.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.metrics;
+
+/**
+ * @author Brett Wooldridge
+ */
+public interface IMetricsTracker extends AutoCloseable
+{
+ default void recordConnectionCreatedMillis(long connectionCreatedMillis) {}
+
+ default void recordConnectionAcquiredNanos(final long elapsedAcquiredNanos) {}
+
+ default void recordConnectionUsageMillis(final long elapsedBorrowedMillis) {}
+
+ default void recordConnectionTimeout() {}
+
+ @Override
+ default void close() {}
+}
diff --git a/src/main/java/com/zaxxer/hikari/metrics/MetricsTracker.java b/src/main/java/com/zaxxer/hikari/metrics/MetricsTracker.java
new file mode 100755
index 0000000..c294fe6
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/metrics/MetricsTracker.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2013,2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.metrics;
+
+/**
+ * This class only supports realtime, not historical metrics.
+ *
+ * @author Brett Wooldridge
+ */
+@Deprecated
+public class MetricsTracker implements IMetricsTracker
+{
+}
diff --git a/src/main/java/com/zaxxer/hikari/metrics/MetricsTrackerFactory.java b/src/main/java/com/zaxxer/hikari/metrics/MetricsTrackerFactory.java
new file mode 100644
index 0000000..3b1c8c1
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/metrics/MetricsTrackerFactory.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2013,2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.metrics;
+
+public interface MetricsTrackerFactory
+{
+ /**
+ * Create an instance of an IMetricsTracker.
+ *
+ * @param poolName the name of the pool
+ * @param poolStats a PoolStats instance to use
+ * @return a IMetricsTracker implementation instance
+ */
+ IMetricsTracker create(String poolName, PoolStats poolStats);
+}
diff --git a/src/main/java/com/zaxxer/hikari/metrics/PoolStats.java b/src/main/java/com/zaxxer/hikari/metrics/PoolStats.java
new file mode 100644
index 0000000..e8905d1
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/metrics/PoolStats.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2015 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.metrics;
+
+import static com.zaxxer.hikari.util.ClockSource.currentTime;
+import static com.zaxxer.hikari.util.ClockSource.plusMillis;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ *
+ * @author Brett Wooldridge
+ */
+public abstract class PoolStats
+{
+ private final AtomicLong reloadAt;
+ private final long timeoutMs;
+
+ protected volatile int totalConnections;
+ protected volatile int idleConnections;
+ protected volatile int activeConnections;
+ protected volatile int pendingThreads;
+
+ public PoolStats(final long timeoutMs)
+ {
+ this.timeoutMs = timeoutMs;
+ this.reloadAt = new AtomicLong();
+ }
+
+ public int getTotalConnections()
+ {
+ if (shouldLoad()) {
+ update();
+ }
+
+ return totalConnections;
+ }
+
+ public int getIdleConnections()
+ {
+ if (shouldLoad()) {
+ update();
+ }
+
+ return idleConnections;
+ }
+
+ public int getActiveConnections()
+ {
+ if (shouldLoad()) {
+ update();
+ }
+
+ return activeConnections;
+ }
+
+ public int getPendingThreads()
+ {
+ if (shouldLoad()) {
+ update();
+ }
+
+ return pendingThreads;
+ }
+
+ protected abstract void update();
+
+ private boolean shouldLoad()
+ {
+ for (; ; ) {
+ final long now = currentTime();
+ final long reloadTime = reloadAt.get();
+ if (reloadTime > now) {
+ return false;
+ }
+ else if (reloadAt.compareAndSet(reloadTime, plusMillis(now, timeoutMs))) {
+ return true;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/metrics/dropwizard/CodaHaleMetricsTracker.java b/src/main/java/com/zaxxer/hikari/metrics/dropwizard/CodaHaleMetricsTracker.java
new file mode 100755
index 0000000..5be5e23
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/metrics/dropwizard/CodaHaleMetricsTracker.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2013,2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.metrics.dropwizard;
+
+import java.util.concurrent.TimeUnit;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import com.zaxxer.hikari.metrics.IMetricsTracker;
+import com.zaxxer.hikari.metrics.PoolStats;
+
+public final class CodaHaleMetricsTracker implements IMetricsTracker
+{
+ private final String poolName;
+ private final Timer connectionObtainTimer;
+ private final Histogram connectionUsage;
+ private final Histogram connectionCreation;
+ private final Meter connectionTimeoutMeter;
+ private final MetricRegistry registry;
+
+ private static final String METRIC_CATEGORY = "pool";
+ private static final String METRIC_NAME_WAIT = "Wait";
+ private static final String METRIC_NAME_USAGE = "Usage";
+ private static final String METRIC_NAME_CONNECT = "ConnectionCreation";
+ private static final String METRIC_NAME_TIMEOUT_RATE = "ConnectionTimeoutRate";
+ private static final String METRIC_NAME_TOTAL_CONNECTIONS = "TotalConnections";
+ private static final String METRIC_NAME_IDLE_CONNECTIONS = "IdleConnections";
+ private static final String METRIC_NAME_ACTIVE_CONNECTIONS = "ActiveConnections";
+ private static final String METRIC_NAME_PENDING_CONNECTIONS = "PendingConnections";
+
+ public CodaHaleMetricsTracker(final String poolName, final PoolStats poolStats, final MetricRegistry registry)
+ {
+ this.poolName = poolName;
+ this.registry = registry;
+ this.connectionObtainTimer = registry.timer(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_WAIT));
+ this.connectionUsage = registry.histogram(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_USAGE));
+ this.connectionCreation = registry.histogram(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_CONNECT));
+ this.connectionTimeoutMeter = registry.meter(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_TIMEOUT_RATE));
+
+ registry.register(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_TOTAL_CONNECTIONS),
+ new Gauge<Integer>() {
+ @Override
+ public Integer getValue() {
+ return poolStats.getTotalConnections();
+ }
+ });
+
+ registry.register(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_IDLE_CONNECTIONS),
+ new Gauge<Integer>() {
+ @Override
+ public Integer getValue() {
+ return poolStats.getIdleConnections();
+ }
+ });
+
+ registry.register(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_ACTIVE_CONNECTIONS),
+ new Gauge<Integer>() {
+ @Override
+ public Integer getValue() {
+ return poolStats.getActiveConnections();
+ }
+ });
+
+ registry.register(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_PENDING_CONNECTIONS),
+ new Gauge<Integer>() {
+ @Override
+ public Integer getValue() {
+ return poolStats.getPendingThreads();
+ }
+ });
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void close()
+ {
+ registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_WAIT));
+ registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_USAGE));
+ registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_CONNECT));
+ registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_TIMEOUT_RATE));
+ registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_TOTAL_CONNECTIONS));
+ registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_IDLE_CONNECTIONS));
+ registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_ACTIVE_CONNECTIONS));
+ registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_PENDING_CONNECTIONS));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void recordConnectionAcquiredNanos(final long elapsedAcquiredNanos)
+ {
+ connectionObtainTimer.update(elapsedAcquiredNanos, TimeUnit.NANOSECONDS);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void recordConnectionUsageMillis(final long elapsedBorrowedMillis)
+ {
+ connectionUsage.update(elapsedBorrowedMillis);
+ }
+
+ @Override
+ public void recordConnectionTimeout()
+ {
+ connectionTimeoutMeter.mark();
+ }
+
+ @Override
+ public void recordConnectionCreatedMillis(long connectionCreatedMillis)
+ {
+ connectionCreation.update(connectionCreatedMillis);
+ }
+
+ public Timer getConnectionAcquisitionTimer()
+ {
+ return connectionObtainTimer;
+ }
+
+ public Histogram getConnectionDurationHistogram()
+ {
+ return connectionUsage;
+ }
+
+ public Histogram getConnectionCreationHistogram()
+ {
+ return connectionCreation;
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/metrics/dropwizard/CodahaleHealthChecker.java b/src/main/java/com/zaxxer/hikari/metrics/dropwizard/CodahaleHealthChecker.java
new file mode 100644
index 0000000..5701dac
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/metrics/dropwizard/CodahaleHealthChecker.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.metrics.dropwizard;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Properties;
+import java.util.SortedMap;
+import java.util.concurrent.TimeUnit;
+
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.health.HealthCheck;
+import com.codahale.metrics.health.HealthCheckRegistry;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.pool.HikariPool;
+
+/**
+ * Provides Dropwizard HealthChecks. Two health checks are provided:
+ * <ul>
+ * <li>ConnectivityCheck</li>
+ * <li>Connection99Percent</li>
+ * </ul>
+ * The ConnectivityCheck will use the <code>connectionTimeout</code>, unless the health check property
+ * <code>connectivityCheckTimeoutMs</code> is defined. However, if either the <code>connectionTimeout</code>
+ * or the <code>connectivityCheckTimeoutMs</code> is 0 (infinite), a timeout of 10 seconds will be used.
+ * <p>
+ * The Connection99Percent health check will only be registered if the health check property
+ * <code>expected99thPercentileMs</code> is defined and greater than 0.
+ *
+ * @author Brett Wooldridge
+ */
+public final class CodahaleHealthChecker
+{
+ /**
+ * Register Dropwizard health checks.
+ *
+ * @param pool the pool to register health checks for
+ * @param hikariConfig the pool configuration
+ * @param registry the HealthCheckRegistry into which checks will be registered
+ */
+ public static void registerHealthChecks(final HikariPool pool, final HikariConfig hikariConfig, final HealthCheckRegistry registry)
+ {
+ final Properties healthCheckProperties = hikariConfig.getHealthCheckProperties();
+ final MetricRegistry metricRegistry = (MetricRegistry) hikariConfig.getMetricRegistry();
+
+ final long checkTimeoutMs = Long.parseLong(healthCheckProperties.getProperty("connectivityCheckTimeoutMs", String.valueOf(hikariConfig.getConnectionTimeout())));
+ registry.register(MetricRegistry.name(hikariConfig.getPoolName(), "pool", "ConnectivityCheck"), new ConnectivityHealthCheck(pool, checkTimeoutMs));
+
+ final long expected99thPercentile = Long.parseLong(healthCheckProperties.getProperty("expected99thPercentileMs", "0"));
+ if (metricRegistry != null && expected99thPercentile > 0) {
+ SortedMap<String,Timer> timers = metricRegistry.getTimers(new MetricFilter() {
+ @Override
+ public boolean matches(String name, Metric metric)
+ {
+ return name.equals(MetricRegistry.name(hikariConfig.getPoolName(), "pool", "Wait"));
+ }
+ });
+
+ if (!timers.isEmpty()) {
+ final Timer timer = timers.entrySet().iterator().next().getValue();
+ registry.register(MetricRegistry.name(hikariConfig.getPoolName(), "pool", "Connection99Percent"), new Connection99Percent(timer, expected99thPercentile));
+ }
+ }
+ }
+
+ private CodahaleHealthChecker()
+ {
+ // private constructor
+ }
+
+ private static class ConnectivityHealthCheck extends HealthCheck
+ {
+ private final HikariPool pool;
+ private final long checkTimeoutMs;
+
+ ConnectivityHealthCheck(final HikariPool pool, final long checkTimeoutMs)
+ {
+ this.pool = pool;
+ this.checkTimeoutMs = (checkTimeoutMs > 0 && checkTimeoutMs != Integer.MAX_VALUE ? checkTimeoutMs : TimeUnit.SECONDS.toMillis(10));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected Result check() throws Exception
+ {
+ try (Connection connection = pool.getConnection(checkTimeoutMs)) {
+ return Result.healthy();
+ }
+ catch (SQLException e) {
+ return Result.unhealthy(e);
+ }
+ }
+ }
+
+ private static class Connection99Percent extends HealthCheck
+ {
+ private final Timer waitTimer;
+ private final long expected99thPercentile;
+
+ Connection99Percent(final Timer waitTimer, final long expected99thPercentile)
+ {
+ this.waitTimer = waitTimer;
+ this.expected99thPercentile = expected99thPercentile;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected Result check() throws Exception
+ {
+ final long the99thPercentile = TimeUnit.NANOSECONDS.toMillis(Math.round(waitTimer.getSnapshot().get99thPercentile()));
+ return the99thPercentile <= expected99thPercentile ? Result.healthy() : Result.unhealthy("99th percentile connection wait time of %dms exceeds the threshold %dms", the99thPercentile, expected99thPercentile);
+ }
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/metrics/dropwizard/CodahaleMetricsTrackerFactory.java b/src/main/java/com/zaxxer/hikari/metrics/dropwizard/CodahaleMetricsTrackerFactory.java
new file mode 100644
index 0000000..d978edd
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/metrics/dropwizard/CodahaleMetricsTrackerFactory.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.metrics.dropwizard;
+
+import com.codahale.metrics.MetricRegistry;
+import com.zaxxer.hikari.metrics.IMetricsTracker;
+import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
+import com.zaxxer.hikari.metrics.PoolStats;
+
+public final class CodahaleMetricsTrackerFactory implements MetricsTrackerFactory
+{
+ private final MetricRegistry registry;
+
+ public CodahaleMetricsTrackerFactory(MetricRegistry registry)
+ {
+ this.registry = registry;
+ }
+
+ public MetricRegistry getRegistry()
+ {
+ return registry;
+ }
+
+ @Override
+ public IMetricsTracker create(String poolName, PoolStats poolStats)
+ {
+ return new CodaHaleMetricsTracker(poolName, poolStats, registry);
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java b/src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java
new file mode 100644
index 0000000..3bff974
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.metrics.prometheus;
+
+import com.zaxxer.hikari.metrics.PoolStats;
+import io.prometheus.client.Collector;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+class HikariCPCollector extends Collector {
+ private final PoolStats poolStats;
+ private final List<String> labelNames;
+ private final List<String> labelValues;
+
+ HikariCPCollector(String poolName, PoolStats poolStats) {
+ this.poolStats = poolStats;
+ this.labelNames = Collections.singletonList("pool");
+ this.labelValues = Collections.singletonList(poolName);
+ }
+
+ @Override
+ public List<MetricFamilySamples> collect() {
+ return Arrays.asList(
+ createSample("hikaricp_active_connections", "Active connections", poolStats.getActiveConnections()),
+ createSample("hikaricp_idle_connections", "Idle connections", poolStats.getIdleConnections()),
+ createSample("hikaricp_pending_threads", "Pending threads", poolStats.getPendingThreads()),
+ createSample("hikaricp_connections", "The number of current connections", poolStats.getTotalConnections())
+ );
+ }
+
+ private MetricFamilySamples createSample(String name, String helpMessage, double value)
+ {
+ List<MetricFamilySamples.Sample> samples = Collections.singletonList(
+ new MetricFamilySamples.Sample(name, labelNames, labelValues, value)
+ );
+
+ return new MetricFamilySamples(name, Type.GAUGE, helpMessage, samples);
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java
new file mode 100644
index 0000000..0bd54fc
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.metrics.prometheus;
+
+import com.zaxxer.hikari.metrics.IMetricsTracker;
+
+import io.prometheus.client.Collector;
+import io.prometheus.client.CollectorRegistry;
+import io.prometheus.client.Counter;
+import io.prometheus.client.Summary;
+
+class PrometheusMetricsTracker implements IMetricsTracker
+{
+ private final Counter.Child connectionTimeoutCounter;
+ private final Summary.Child elapsedAcquiredSummary;
+ private final Summary.Child elapsedBorrowedSummary;
+ private final Summary.Child elapsedCreationSummary;
+
+ private final Counter ctCounter;
+ private final Summary eaSummary;
+ private final Summary ebSummary;
+ private final Summary ecSummary;
+ private final Collector collector;
+
+ PrometheusMetricsTracker(String poolName, Collector collector)
+ {
+ this.collector = collector;
+
+ ctCounter = Counter.build()
+ .name("hikaricp_connection_timeout_count")
+ .labelNames("pool")
+ .help("Connection timeout count")
+ .register();
+
+ this.connectionTimeoutCounter = ctCounter.labels(poolName);
+
+ eaSummary = Summary.build()
+ .name("hikaricp_connection_acquired_nanos")
+ .labelNames("pool")
+ .help("Connection acquired time (ns)")
+ .register();
+ this.elapsedAcquiredSummary = eaSummary.labels(poolName);
+
+ ebSummary = Summary.build()
+ .name("hikaricp_connection_usage_millis")
+ .labelNames("pool")
+ .help("Connection usage (ms)")
+ .register();
+ this.elapsedBorrowedSummary = ebSummary.labels(poolName);
+
+ ecSummary = Summary.build()
+ .name("hikaricp_connection_creation_millis")
+ .labelNames("pool")
+ .help("Connection creation (ms)")
+ .register();
+ this.elapsedCreationSummary = ecSummary.labels(poolName);
+ }
+
+ @Override
+ public void close()
+ {
+ CollectorRegistry.defaultRegistry.unregister(ctCounter);
+ CollectorRegistry.defaultRegistry.unregister(eaSummary);
+ CollectorRegistry.defaultRegistry.unregister(ebSummary);
+ CollectorRegistry.defaultRegistry.unregister(ecSummary);
+ CollectorRegistry.defaultRegistry.unregister(collector);
+ }
+
+ @Override
+ public void recordConnectionAcquiredNanos(long elapsedAcquiredNanos)
+ {
+ elapsedAcquiredSummary.observe(elapsedAcquiredNanos);
+ }
+
+ @Override
+ public void recordConnectionUsageMillis(long elapsedBorrowedMillis)
+ {
+ elapsedBorrowedSummary.observe(elapsedBorrowedMillis);
+ }
+
+ @Override
+ public void recordConnectionCreatedMillis(long connectionCreatedMillis)
+ {
+ elapsedCreationSummary.observe(connectionCreatedMillis);
+ }
+
+ @Override
+ public void recordConnectionTimeout()
+ {
+ connectionTimeoutCounter.inc();
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java
new file mode 100644
index 0000000..2a0b5c3
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.metrics.prometheus;
+
+import com.zaxxer.hikari.metrics.IMetricsTracker;
+import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
+import com.zaxxer.hikari.metrics.PoolStats;
+
+import io.prometheus.client.Collector;
+
+/**
+ * <pre>{@code
+ * HikariConfig config = new HikariConfig();
+ * config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
+ * }</pre>
+ */
+public class PrometheusMetricsTrackerFactory implements MetricsTrackerFactory
+{
+ @Override
+ public IMetricsTracker create(String poolName, PoolStats poolStats)
+ {
+ Collector collector = new HikariCPCollector(poolName, poolStats).register();
+ return new PrometheusMetricsTracker(poolName, collector);
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/pool/HikariPool.java b/src/main/java/com/zaxxer/hikari/pool/HikariPool.java
new file mode 100755
index 0000000..e9b7364
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/pool/HikariPool.java
@@ -0,0 +1,719 @@
+/*
+ * Copyright (C) 2013,2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.pool;
+
+import static java.util.Collections.unmodifiableCollection;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import static com.zaxxer.hikari.pool.PoolEntry.LASTACCESS_REVERSE_COMPARABLE;
+import static com.zaxxer.hikari.util.ClockSource.currentTime;
+import static com.zaxxer.hikari.util.ClockSource.elapsedDisplayString;
+import static com.zaxxer.hikari.util.ClockSource.elapsedMillis;
+import static com.zaxxer.hikari.util.ClockSource.plusMillis;
+import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_IN_USE;
+import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_NOT_IN_USE;
+import static com.zaxxer.hikari.util.UtilityElf.createThreadPoolExecutor;
+import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLTimeoutException;
+import java.sql.SQLTransientConnectionException;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.health.HealthCheckRegistry;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariPoolMXBean;
+import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
+import com.zaxxer.hikari.metrics.PoolStats;
+import com.zaxxer.hikari.metrics.dropwizard.CodahaleHealthChecker;
+import com.zaxxer.hikari.metrics.dropwizard.CodahaleMetricsTrackerFactory;
+import com.zaxxer.hikari.util.ConcurrentBag;
+import com.zaxxer.hikari.util.ConcurrentBag.IBagStateListener;
+import com.zaxxer.hikari.util.SuspendResumeLock;
+import com.zaxxer.hikari.util.UtilityElf.DefaultThreadFactory;
+
+/**
+ * This is the primary connection pool class that provides the basic
+ * pooling behavior for HikariCP.
+ *
+ * @author Brett Wooldridge
+ */
+public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateListener
+{
+ private final Logger LOGGER = LoggerFactory.getLogger(HikariPool.class);
+
+ private static final int POOL_NORMAL = 0;
+ private static final int POOL_SUSPENDED = 1;
+ private static final int POOL_SHUTDOWN = 2;
+
+ private volatile int poolState;
+
+ private final long ALIVE_BYPASS_WINDOW_MS = Long.getLong("com.zaxxer.hikari.aliveBypassWindowMs", MILLISECONDS.toMillis(500));
+ private final long HOUSEKEEPING_PERIOD_MS = Long.getLong("com.zaxxer.hikari.housekeeping.periodMs", SECONDS.toMillis(30));
+
+ private final PoolEntryCreator POOL_ENTRY_CREATOR = new PoolEntryCreator(null);
+ private final Collection<Runnable> addConnectionQueue;
+ private final ThreadPoolExecutor addConnectionExecutor;
+ private final ThreadPoolExecutor closeConnectionExecutor;
+
+ private final ConcurrentBag<PoolEntry> connectionBag;
+
+ private final ProxyLeakTask leakTask;
+ private final SuspendResumeLock suspendResumeLock;
+
+ private ScheduledExecutorService houseKeepingExecutorService;
+ private ScheduledFuture<?> houseKeeperTask;
+
+ /**
+ * Construct a HikariPool with the specified configuration.
+ *
+ * @param config a HikariConfig instance
+ */
+ public HikariPool(final HikariConfig config)
+ {
+ super(config);
+
+ this.connectionBag = new ConcurrentBag<>(this);
+ this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
+
+ initializeHouseKeepingExecutorService();
+
+ checkFailFast();
+
+ if (config.getMetricsTrackerFactory() != null) {
+ setMetricsTrackerFactory(config.getMetricsTrackerFactory());
+ }
+ else {
+ setMetricRegistry(config.getMetricRegistry());
+ }
+
+ setHealthCheckRegistry(config.getHealthCheckRegistry());
+
+ registerMBeans(this);
+
+ ThreadFactory threadFactory = config.getThreadFactory();
+
+ LinkedBlockingQueue<Runnable> addConnectionQueue = new LinkedBlockingQueue<>(config.getMaximumPoolSize());
+ this.addConnectionQueue = unmodifiableCollection(addConnectionQueue);
+ this.addConnectionExecutor = createThreadPoolExecutor(addConnectionQueue, poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardPolicy());
+ this.closeConnectionExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), poolName + " connection closer", threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
+
+ this.leakTask = new ProxyLeakTask(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
+
+ this.houseKeeperTask = this.houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, HOUSEKEEPING_PERIOD_MS, MILLISECONDS);
+ }
+
+ /**
+ * Get a connection from the pool, or timeout after connectionTimeout milliseconds.
+ *
+ * @return a java.sql.Connection instance
+ * @throws SQLException thrown if a timeout occurs trying to obtain a connection
+ */
+ public Connection getConnection() throws SQLException
+ {
+ return getConnection(connectionTimeout);
+ }
+
+ /**
+ * Get a connection from the pool, or timeout after the specified number of milliseconds.
+ *
+ * @param hardTimeout the maximum time to wait for a connection from the pool
+ * @return a java.sql.Connection instance
+ * @throws SQLException thrown if a timeout occurs trying to obtain a connection
+ */
+ public Connection getConnection(final long hardTimeout) throws SQLException
+ {
+ suspendResumeLock.acquire();
+ final long startTime = currentTime();
+
+ try {
+ long timeout = hardTimeout;
+ PoolEntry poolEntry = null;
+ try {
+ do {
+ poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
+ if (poolEntry == null) {
+ break; // We timed out... break and throw exception
+ }
+
+ final long now = currentTime();
+ if (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > ALIVE_BYPASS_WINDOW_MS && !isConnectionAlive(poolEntry.connection))) {
+ closeConnection(poolEntry, "(connection is evicted or dead)"); // Throw away the dead connection (passed max age or failed alive test)
+ timeout = hardTimeout - elapsedMillis(startTime);
+ }
+ else {
+ metricsTracker.recordBorrowStats(poolEntry, startTime);
+ return poolEntry.createProxyConnection(leakTask.schedule(poolEntry), now);
+ }
+ } while (timeout > 0L);
+ }
+ catch (InterruptedException e) {
+ if (poolEntry != null) {
+ poolEntry.recycle(startTime);
+ }
+ Thread.currentThread().interrupt();
+ throw new SQLException(poolName + " - Interrupted during connection acquisition", e);
+ }
+ }
+ finally {
+ suspendResumeLock.release();
+ }
+
+ throw createTimeoutException(startTime);
+ }
+
+ /**
+ * Shutdown the pool, closing all idle connections and aborting or closing
+ * active connections.
+ *
+ * @throws InterruptedException thrown if the thread is interrupted during shutdown
+ */
+ public synchronized void shutdown() throws InterruptedException
+ {
+ try {
+ poolState = POOL_SHUTDOWN;
+
+ if (addConnectionExecutor == null) {
+ return;
+ }
+
+ logPoolState("Before shutdown ");
+
+ if (houseKeeperTask != null) {
+ houseKeeperTask.cancel(false);
+ houseKeeperTask = null;
+ }
+
+ softEvictConnections();
+
+ addConnectionExecutor.shutdown();
+ addConnectionExecutor.awaitTermination(5L, SECONDS);
+
+ destroyHouseKeepingExecutorService();
+
+ connectionBag.close();
+
+ final ExecutorService assassinExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), poolName + " connection assassinator",
+ config.getThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
+ try {
+ final long start = currentTime();
+ do {
+ abortActiveConnections(assassinExecutor);
+ softEvictConnections();
+ } while (getTotalConnections() > 0 && elapsedMillis(start) < SECONDS.toMillis(5));
+ }
+ finally {
+ assassinExecutor.shutdown();
+ assassinExecutor.awaitTermination(5L, SECONDS);
+ }
+
+ shutdownNetworkTimeoutExecutor();
+ closeConnectionExecutor.shutdown();
+ closeConnectionExecutor.awaitTermination(5L, SECONDS);
+ }
+ finally {
+ logPoolState("After shutdown ");
+ unregisterMBeans();
+ metricsTracker.close();
+ }
+ }
+
+ /**
+ * Evict a connection from the pool.
+ *
+ * @param connection the connection to evict
+ */
+ public void evictConnection(Connection connection)
+ {
+ ProxyConnection proxyConnection = (ProxyConnection) connection;
+ proxyConnection.cancelLeakTask();
+
+ try {
+ softEvictConnection(proxyConnection.getPoolEntry(), "(connection evicted by user)", !connection.isClosed() /* owner */);
+ }
+ catch (SQLException e) {
+ // unreachable in HikariCP, but we're still forced to catch it
+ }
+ }
+
+ public void setMetricRegistry(Object metricRegistry)
+ {
+ if (metricRegistry != null) {
+ setMetricsTrackerFactory(new CodahaleMetricsTrackerFactory((MetricRegistry) metricRegistry));
+ }
+ else {
+ setMetricsTrackerFactory(null);
+ }
+ }
+
+ public void setMetricsTrackerFactory(MetricsTrackerFactory metricsTrackerFactory)
+ {
+ if (metricsTrackerFactory != null) {
+ this.metricsTracker = new MetricsTrackerDelegate(metricsTrackerFactory.create(config.getPoolName(), getPoolStats()));
+ }
+ else {
+ this.metricsTracker = new NopMetricsTrackerDelegate();
+ }
+ }
+
+ public void setHealthCheckRegistry(Object healthCheckRegistry)
+ {
+ if (healthCheckRegistry != null) {
+ CodahaleHealthChecker.registerHealthChecks(this, config, (HealthCheckRegistry) healthCheckRegistry);
+ }
+ }
+
+ // ***********************************************************************
+ // IBagStateListener callback
+ // ***********************************************************************
+
+ /** {@inheritDoc} */
+ @Override
+ public Future<Boolean> addBagItem(final int waiting)
+ {
+ final boolean shouldAdd = waiting - addConnectionQueue.size() >= 0; // Yes, >= is intentional.
+ if (shouldAdd) {
+ return addConnectionExecutor.submit(POOL_ENTRY_CREATOR);
+ }
+
+ return CompletableFuture.completedFuture(Boolean.TRUE);
+ }
+
+ // ***********************************************************************
+ // HikariPoolMBean methods
+ // ***********************************************************************
+
+ /** {@inheritDoc} */
+ @Override
+ public int getActiveConnections()
+ {
+ return connectionBag.getCount(STATE_IN_USE);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getIdleConnections()
+ {
+ return connectionBag.getCount(STATE_NOT_IN_USE);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getTotalConnections()
+ {
+ return connectionBag.size();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getThreadsAwaitingConnection()
+ {
+ return connectionBag.getWaitingThreadCount();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void softEvictConnections()
+ {
+ connectionBag.values().forEach(poolEntry -> softEvictConnection(poolEntry, "(connection evicted)", false /* not owner */));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public synchronized void suspendPool()
+ {
+ if (suspendResumeLock == SuspendResumeLock.FAUX_LOCK) {
+ throw new IllegalStateException(poolName + " - is not suspendable");
+ }
+ else if (poolState != POOL_SUSPENDED) {
+ suspendResumeLock.suspend();
+ poolState = POOL_SUSPENDED;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public synchronized void resumePool()
+ {
+ if (poolState == POOL_SUSPENDED) {
+ poolState = POOL_NORMAL;
+ fillPool();
+ suspendResumeLock.resume();
+ }
+ }
+
+ // ***********************************************************************
+ // Package methods
+ // ***********************************************************************
+
+ /**
+ * Log the current pool state at debug level.
+ *
+ * @param prefix an optional prefix to prepend the log message
+ */
+ void logPoolState(String... prefix)
+ {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("{} - {}stats (total={}, active={}, idle={}, waiting={})",
+ poolName, (prefix.length > 0 ? prefix[0] : ""),
+ getTotalConnections(), getActiveConnections(), getIdleConnections(), getThreadsAwaitingConnection());
+ }
+ }
+
+ /**
+ * Recycle PoolEntry (add back to the pool)
+ *
+ * @param poolEntry the PoolEntry to recycle
+ */
+ @Override
+ void recycle(final PoolEntry poolEntry)
+ {
+ metricsTracker.recordConnectionUsage(poolEntry);
+
+ connectionBag.requite(poolEntry);
+ }
+
+ /**
+ * Permanently close the real (underlying) connection (eat any exception).
+ *
+ * @param poolEntry poolEntry having the connection to close
+ * @param closureReason reason to close
+ */
+ void closeConnection(final PoolEntry poolEntry, final String closureReason)
+ {
+ if (connectionBag.remove(poolEntry)) {
+ final Connection connection = poolEntry.close();
+ closeConnectionExecutor.execute(() -> {
+ quietlyCloseConnection(connection, closureReason);
+ if (poolState == POOL_NORMAL) {
+ fillPool();
+ }
+ });
+ }
+ }
+
+ int[] getPoolStateCounts()
+ {
+ return connectionBag.getStateCounts();
+ }
+
+ // ***********************************************************************
+ // Private methods
+ // ***********************************************************************
+
+ /**
+ * Creating new poolEntry.
+ */
+ private PoolEntry createPoolEntry()
+ {
+ try {
+ final PoolEntry poolEntry = newPoolEntry();
+
+ final long maxLifetime = config.getMaxLifetime();
+ if (maxLifetime > 0) {
+ // variance up to 2.5% of the maxlifetime
+ final long variance = maxLifetime > 10_000 ? ThreadLocalRandom.current().nextLong( maxLifetime / 40 ) : 0;
+ final long lifetime = maxLifetime - variance;
+ poolEntry.setFutureEol(houseKeepingExecutorService.schedule(
+ () -> softEvictConnection(poolEntry, "(connection has passed maxLifetime)", false /* not owner */),
+ lifetime, MILLISECONDS));
+ }
+
+ return poolEntry;
+ }
+ catch (Exception e) {
+ if (poolState == POOL_NORMAL) {
+ LOGGER.debug("{} - Cannot acquire connection from data source", poolName, (e instanceof ConnectionSetupException ? e.getCause() : e));
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Fill pool up from current idle connections (as they are perceived at the point of execution) to minimumIdle connections.
+ */
+ private synchronized void fillPool()
+ {
+ final int connectionsToAdd = Math.min(config.getMaximumPoolSize() - getTotalConnections(), config.getMinimumIdle() - getIdleConnections())
+ - addConnectionQueue.size();
+ for (int i = 0; i < connectionsToAdd; i++) {
+ addConnectionExecutor.submit((i < connectionsToAdd - 1) ? POOL_ENTRY_CREATOR : new PoolEntryCreator("After adding "));
+ }
+ }
+
+ /**
+ * Attempt to abort or close active connections.
+ */
+ private void abortActiveConnections(final ExecutorService assassinExecutor)
+ {
+ for (PoolEntry poolEntry : connectionBag.values(STATE_IN_USE)) {
+ Connection connection = poolEntry.close();
+ try {
+ connection.abort(assassinExecutor);
+ }
+ catch (Throwable e) {
+ quietlyCloseConnection(connection, "(connection aborted during shutdown)");
+ }
+ finally {
+ connectionBag.remove(poolEntry);
+ }
+ }
+ }
+
+ /**
+ * If initializationFailFast is configured, check that we have DB connectivity.
+ *
+ * @throws PoolInitializationException if fails to create or validate connection
+ */
+ private void checkFailFast()
+ {
+ final long startTime = currentTime();
+ Throwable throwable = new SQLTimeoutException("HikariCP was unable to initialize connections in pool " + poolName);
+ do {
+ final PoolEntry poolEntry = createPoolEntry();
+ if (poolEntry != null) {
+ if (config.getMinimumIdle() > 0) {
+ connectionBag.add(poolEntry);
+ LOGGER.debug("{} - Added connection {}", poolName, poolEntry.connection);
+ }
+ else {
+ final Connection connection = poolEntry.close();
+ quietlyCloseConnection(connection, "(initialization check complete and minimumIdle is zero)");
+ }
+
+ return;
+ }
+
+ throwable = getLastConnectionFailure();
+ if (throwable instanceof ConnectionSetupException) {
+ throwPoolInitializationException(throwable.getCause());
+ }
+
+ quietlySleep(1000L);
+ } while (elapsedMillis(startTime) < config.getInitializationFailTimeout());
+
+ if (config.getInitializationFailTimeout() > 0) {
+ throwPoolInitializationException(throwable);
+ }
+ }
+
+ private void throwPoolInitializationException(Throwable t)
+ {
+ LOGGER.error("{} - Exception during pool initialization.", poolName, t);
+ destroyHouseKeepingExecutorService();
+ throw new PoolInitializationException(t);
+ }
+
+ private void softEvictConnection(final PoolEntry poolEntry, final String reason, final boolean owner)
+ {
+ poolEntry.markEvicted();
+ if (owner || connectionBag.reserve(poolEntry)) {
+ closeConnection(poolEntry, reason);
+ }
+ }
+
+ private void initializeHouseKeepingExecutorService()
+ {
+ if (config.getScheduledExecutor() == null) {
+ final ThreadFactory threadFactory = Optional.ofNullable(config.getThreadFactory()).orElse(new DefaultThreadFactory(poolName + " housekeeper", true));
+ final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, threadFactory, new ThreadPoolExecutor.DiscardPolicy());
+ executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
+ executor.setRemoveOnCancelPolicy(true);
+ this.houseKeepingExecutorService = executor;
+ }
+ else {
+ this.houseKeepingExecutorService = config.getScheduledExecutor();
+ }
+ }
+
+ private void destroyHouseKeepingExecutorService()
+ {
+ if (config.getScheduledExecutor() == null) {
+ houseKeepingExecutorService.shutdownNow();
+ }
+ }
+
+ private PoolStats getPoolStats()
+ {
+ return new PoolStats(SECONDS.toMillis(1)) {
+ @Override
+ protected void update() {
+ this.pendingThreads = HikariPool.this.getThreadsAwaitingConnection();
+ this.idleConnections = HikariPool.this.getIdleConnections();
+ this.totalConnections = HikariPool.this.getTotalConnections();
+ this.activeConnections = HikariPool.this.getActiveConnections();
+ }
+ };
+ }
+
+ private SQLException createTimeoutException(long startTime)
+ {
+ logPoolState("Timeout failure ");
+ metricsTracker.recordConnectionTimeout();
+
+ String sqlState = null;
+ final Throwable originalException = getLastConnectionFailure();
+ if (originalException instanceof SQLException) {
+ sqlState = ((SQLException) originalException).getSQLState();
+ }
+ final SQLException connectionException = new SQLTransientConnectionException(poolName + " - Connection is not available, request timed out after " + elapsedMillis(startTime) + "ms.", sqlState, originalException);
+ if (originalException instanceof SQLException) {
+ connectionException.setNextException((SQLException) originalException);
+ }
+
+ return connectionException;
+ }
+
+ // ***********************************************************************
+ // Non-anonymous Inner-classes
+ // ***********************************************************************
+
+ /**
+ * Creating and adding poolEntries (connections) to the pool.
+ */
+ private final class PoolEntryCreator implements Callable<Boolean>
+ {
+ private final String afterPrefix;
+
+ PoolEntryCreator(String afterPrefix)
+ {
+ this.afterPrefix = afterPrefix;
+ }
+
+ @Override
+ public Boolean call() throws Exception
+ {
+ long sleepBackoff = 250L;
+ while (poolState == POOL_NORMAL && shouldCreateAnotherConnection()) {
+ final PoolEntry poolEntry = createPoolEntry();
+ if (poolEntry != null) {
+ connectionBag.add(poolEntry);
+ LOGGER.debug("{} - Added connection {}", poolName, poolEntry.connection);
+ if (afterPrefix != null) {
+ logPoolState(afterPrefix);
+ }
+ return Boolean.TRUE;
+ }
+
+ // failed to get connection from db, sleep and retry
+ quietlySleep(sleepBackoff);
+ sleepBackoff = Math.min(SECONDS.toMillis(10), Math.min(connectionTimeout, (long) (sleepBackoff * 1.5)));
+ }
+ // Pool is suspended or shutdown or at max size
+ return Boolean.FALSE;
+ }
+
+ private boolean shouldCreateAnotherConnection() {
+ // only create connections if we need another idle connection or have threads still waiting
+ // for a new connection, otherwise bail
+ return getTotalConnections() < config.getMaximumPoolSize() &&
+ (connectionBag.getWaitingThreadCount() > 0 || getIdleConnections() < config.getMinimumIdle());
+ }
+ }
+
+ /**
+ * The house keeping task to retire and maintain minimum idle connections.
+ */
+ private final class HouseKeeper implements Runnable
+ {
+ private volatile long previous = plusMillis(currentTime(), -HOUSEKEEPING_PERIOD_MS);
+
+ @Override
+ public void run()
+ {
+ try {
+ // refresh timeouts in case they changed via MBean
+ connectionTimeout = config.getConnectionTimeout();
+ validationTimeout = config.getValidationTimeout();
+ leakTask.updateLeakDetectionThreshold(config.getLeakDetectionThreshold());
+
+ final long idleTimeout = config.getIdleTimeout();
+ final long now = currentTime();
+
+ // Detect retrograde time, allowing +128ms as per NTP spec.
+ if (plusMillis(now, 128) < plusMillis(previous, HOUSEKEEPING_PERIOD_MS)) {
+ LOGGER.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.",
+ poolName, elapsedDisplayString(previous, now));
+ previous = now;
+ softEvictConnections();
+ fillPool();
+ return;
+ }
+ else if (now > plusMillis(previous, (3 * HOUSEKEEPING_PERIOD_MS) / 2)) {
+ // No point evicting for forward clock motion, this merely accelerates connection retirement anyway
+ LOGGER.warn("{} - Thread starvation or clock leap detected (housekeeper delta={}).", poolName, elapsedDisplayString(previous, now));
+ }
+
+ previous = now;
+
+ String afterPrefix = "Pool ";
+ if (idleTimeout > 0L && config.getMinimumIdle() < config.getMaximumPoolSize()) {
+ logPoolState("Before cleanup ");
+ afterPrefix = "After cleanup ";
+
+ connectionBag
+ .values(STATE_NOT_IN_USE)
+ .stream()
+ .sorted(LASTACCESS_REVERSE_COMPARABLE)
+ .skip(config.getMinimumIdle())
+ .filter(p -> elapsedMillis(p.lastAccessed, now) > idleTimeout)
+ .filter(p -> connectionBag.reserve(p))
+ .forEachOrdered(p -> closeConnection(p, "(connection has passed idleTimeout)"));
+ }
+
+ logPoolState(afterPrefix);
+
+ fillPool(); // Try to maintain minimum connections
+ }
+ catch (Exception e) {
+ LOGGER.error("Unexpected exception in housekeeping task", e);
+ }
+ }
+ }
+
+ public static class PoolInitializationException extends RuntimeException
+ {
+ private static final long serialVersionUID = 929872118275916520L;
+
+ /**
+ * Construct an exception, possibly wrapping the provided Throwable as the cause.
+ * @param t the Throwable to wrap
+ */
+ public PoolInitializationException(Throwable t)
+ {
+ super("Failed to initialize pool: " + t.getMessage(), t);
+ }
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/pool/PoolBase.java b/src/main/java/com/zaxxer/hikari/pool/PoolBase.java
new file mode 100755
index 0000000..b463dae
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/pool/PoolBase.java
@@ -0,0 +1,703 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.pool;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.lang.management.ManagementFactory;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLTransientConnectionException;
+import java.sql.Statement;
+import java.util.Properties;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.sql.DataSource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.metrics.IMetricsTracker;
+import com.zaxxer.hikari.util.DriverDataSource;
+import com.zaxxer.hikari.util.PropertyElf;
+import com.zaxxer.hikari.util.UtilityElf;
+import com.zaxxer.hikari.util.UtilityElf.DefaultThreadFactory;
+
+import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_AUTOCOMMIT;
+import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_CATALOG;
+import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_ISOLATION;
+import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_NETTIMEOUT;
+import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_READONLY;
+import static com.zaxxer.hikari.util.ClockSource.currentTime;
+import static com.zaxxer.hikari.util.ClockSource.elapsedMillis;
+import static com.zaxxer.hikari.util.ClockSource.elapsedNanos;
+import static com.zaxxer.hikari.util.UtilityElf.createInstance;
+
+abstract class PoolBase
+{
+ private final Logger LOGGER = LoggerFactory.getLogger(PoolBase.class);
+
+ protected final HikariConfig config;
+ protected final String poolName;
+ protected long connectionTimeout;
+ protected long validationTimeout;
+ protected IMetricsTrackerDelegate metricsTracker;
+
+ private static final String[] RESET_STATES = {"readOnly", "autoCommit", "isolation", "catalog", "netTimeout"};
+ private static final int UNINITIALIZED = -1;
+ private static final int TRUE = 1;
+ private static final int FALSE = 0;
+
+ private int networkTimeout;
+ private int isNetworkTimeoutSupported;
+ private int isQueryTimeoutSupported;
+ private int defaultTransactionIsolation;
+ private int transactionIsolation;
+ private Executor netTimeoutExecutor;
+ private DataSource dataSource;
+
+ private final String catalog;
+ private final boolean isReadOnly;
+ private final boolean isAutoCommit;
+
+ private final boolean isUseJdbc4Validation;
+ private final boolean isIsolateInternalQueries;
+ private final AtomicReference<Throwable> lastConnectionFailure;
+
+ private volatile boolean isValidChecked;
+
+ PoolBase(final HikariConfig config)
+ {
+ this.config = config;
+
+ this.networkTimeout = UNINITIALIZED;
+ this.catalog = config.getCatalog();
+ this.isReadOnly = config.isReadOnly();
+ this.isAutoCommit = config.isAutoCommit();
+ this.transactionIsolation = UtilityElf.getTransactionIsolation(config.getTransactionIsolation());
+
+ this.isQueryTimeoutSupported = UNINITIALIZED;
+ this.isNetworkTimeoutSupported = UNINITIALIZED;
+ this.isUseJdbc4Validation = config.getConnectionTestQuery() == null;
+ this.isIsolateInternalQueries = config.isIsolateInternalQueries();
+
+ this.poolName = config.getPoolName();
+ this.connectionTimeout = config.getConnectionTimeout();
+ this.validationTimeout = config.getValidationTimeout();
+ this.lastConnectionFailure = new AtomicReference<>();
+
+ initializeDataSource();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString()
+ {
+ return poolName;
+ }
+
+ abstract void recycle(final PoolEntry poolEntry);
+
+ // ***********************************************************************
+ // JDBC methods
+ // ***********************************************************************
+
+ void quietlyCloseConnection(final Connection connection, final String closureReason)
+ {
+ if (connection != null) {
+ try {
+ LOGGER.debug("{} - Closing connection {}: {}", poolName, connection, closureReason);
+ try {
+ setNetworkTimeout(connection, SECONDS.toMillis(15));
+ }
+ finally {
+ connection.close(); // continue with the close even if setNetworkTimeout() throws
+ }
+ }
+ catch (Throwable e) {
+ LOGGER.debug("{} - Closing connection {} failed", poolName, connection, e);
+ }
+ }
+ }
+
+ boolean isConnectionAlive(final Connection connection)
+ {
+ try {
+ try {
+ if (isUseJdbc4Validation) {
+ return connection.isValid((int) MILLISECONDS.toSeconds(Math.max(1000L, validationTimeout)));
+ }
+
+ setNetworkTimeout(connection, validationTimeout);
+
+ try (Statement statement = connection.createStatement()) {
+ if (isNetworkTimeoutSupported != TRUE) {
+ setQueryTimeout(statement, (int) MILLISECONDS.toSeconds(Math.max(1000L, validationTimeout)));
+ }
+
+ statement.execute(config.getConnectionTestQuery());
+ }
+ }
+ finally {
+ if (isIsolateInternalQueries && !isAutoCommit) {
+ connection.rollback();
+ }
+ }
+
+ setNetworkTimeout(connection, networkTimeout);
+
+ return true;
+ }
+ catch (Exception e) {
+ lastConnectionFailure.set(e);
+ LOGGER.warn("{} - Failed to validate connection {} ({})", poolName, connection, e.getMessage());
+ return false;
+ }
+ }
+
+ Throwable getLastConnectionFailure()
+ {
+ return lastConnectionFailure.get();
+ }
+
+ public DataSource getUnwrappedDataSource()
+ {
+ return dataSource;
+ }
+
+ // ***********************************************************************
+ // PoolEntry methods
+ // ***********************************************************************
+
+ PoolEntry newPoolEntry() throws Exception
+ {
+ return new PoolEntry(newConnection(), this, isReadOnly, isAutoCommit);
+ }
+
+ void resetConnectionState(final Connection connection, final ProxyConnection proxyConnection, final int dirtyBits) throws SQLException
+ {
+ int resetBits = 0;
+
+ if ((dirtyBits & DIRTY_BIT_READONLY) != 0 && proxyConnection.getReadOnlyState() != isReadOnly) {
+ connection.setReadOnly(isReadOnly);
+ resetBits |= DIRTY_BIT_READONLY;
+ }
+
+ if ((dirtyBits & DIRTY_BIT_AUTOCOMMIT) != 0 && proxyConnection.getAutoCommitState() != isAutoCommit) {
+ connection.setAutoCommit(isAutoCommit);
+ resetBits |= DIRTY_BIT_AUTOCOMMIT;
+ }
+
+ if ((dirtyBits & DIRTY_BIT_ISOLATION) != 0 && proxyConnection.getTransactionIsolationState() != transactionIsolation) {
+ connection.setTransactionIsolation(transactionIsolation);
+ resetBits |= DIRTY_BIT_ISOLATION;
+ }
+
+ if ((dirtyBits & DIRTY_BIT_CATALOG) != 0 && catalog != null && !catalog.equals(proxyConnection.getCatalogState())) {
+ connection.setCatalog(catalog);
+ resetBits |= DIRTY_BIT_CATALOG;
+ }
+
+ if ((dirtyBits & DIRTY_BIT_NETTIMEOUT) != 0 && proxyConnection.getNetworkTimeoutState() != networkTimeout) {
+ setNetworkTimeout(connection, networkTimeout);
+ resetBits |= DIRTY_BIT_NETTIMEOUT;
+ }
+
+ if (resetBits != 0 && LOGGER.isDebugEnabled()) {
+ LOGGER.debug("{} - Reset ({}) on connection {}", poolName, stringFromResetBits(resetBits), connection);
+ }
+ }
+
+ void shutdownNetworkTimeoutExecutor()
+ {
+ if (netTimeoutExecutor instanceof ThreadPoolExecutor) {
+ ((ThreadPoolExecutor) netTimeoutExecutor).shutdownNow();
+ }
+ }
+
+ // ***********************************************************************
+ // JMX methods
+ // ***********************************************************************
+
+ /**
+ * Register MBeans for HikariConfig and HikariPool.
+ *
+ * @param pool a HikariPool instance
+ */
+ void registerMBeans(final HikariPool hikariPool)
+ {
+ if (!config.isRegisterMbeans()) {
+ return;
+ }
+
+ try {
+ final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
+
+ final ObjectName beanConfigName = new ObjectName("com.zaxxer.hikari:type=PoolConfig (" + poolName + ")");
+ final ObjectName beanPoolName = new ObjectName("com.zaxxer.hikari:type=Pool (" + poolName + ")");
+ if (!mBeanServer.isRegistered(beanConfigName)) {
+ mBeanServer.registerMBean(config, beanConfigName);
+ mBeanServer.registerMBean(hikariPool, beanPoolName);
+ }
+ else {
+ LOGGER.error("{} - JMX name ({}) is already registered.", poolName, poolName);
+ }
+ }
+ catch (Exception e) {
+ LOGGER.warn("{} - Failed to register management beans.", poolName, e);
+ }
+ }
+
+ /**
+ * Unregister MBeans for HikariConfig and HikariPool.
+ */
+ void unregisterMBeans()
+ {
+ if (!config.isRegisterMbeans()) {
+ return;
+ }
+
+ try {
+ final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
+
+ final ObjectName beanConfigName = new ObjectName("com.zaxxer.hikari:type=PoolConfig (" + poolName + ")");
+ final ObjectName beanPoolName = new ObjectName("com.zaxxer.hikari:type=Pool (" + poolName + ")");
+ if (mBeanServer.isRegistered(beanConfigName)) {
+ mBeanServer.unregisterMBean(beanConfigName);
+ mBeanServer.unregisterMBean(beanPoolName);
+ }
+ }
+ catch (Exception e) {
+ LOGGER.warn("{} - Failed to unregister management beans.", poolName, e);
+ }
+ }
+
+ // ***********************************************************************
+ // Private methods
+ // ***********************************************************************
+
+ /**
+ * Create/initialize the underlying DataSource.
+ *
+ * @return a DataSource instance
+ */
+ private void initializeDataSource()
+ {
+ final String jdbcUrl = config.getJdbcUrl();
+ final String username = config.getUsername();
+ final String password = config.getPassword();
+ final String dsClassName = config.getDataSourceClassName();
+ final String driverClassName = config.getDriverClassName();
+ final Properties dataSourceProperties = config.getDataSourceProperties();
+
+ DataSource dataSource = config.getDataSource();
+ if (dsClassName != null && dataSource == null) {
+ dataSource = createInstance(dsClassName, DataSource.class);
+ PropertyElf.setTargetFromProperties(dataSource, dataSourceProperties);
+ }
+ else if (jdbcUrl != null && dataSource == null) {
+ dataSource = new DriverDataSource(jdbcUrl, driverClassName, dataSourceProperties, username, password);
+ }
+
+ if (dataSource != null) {
+ setLoginTimeout(dataSource);
+ createNetworkTimeoutExecutor(dataSource, dsClassName, jdbcUrl);
+ }
+
+ this.dataSource = dataSource;
+ }
+
+ /**
+ * Obtain connection from data source.
+ *
+ * @return a Connection connection
+ */
+ Connection newConnection() throws Exception
+ {
+ final long start = currentTime();
+
+ Connection connection = null;
+ try {
+ String username = config.getUsername();
+ String password = config.getPassword();
+
+ connection = (username == null) ? dataSource.getConnection() : dataSource.getConnection(username, password);
+ if (connection == null) {
+ throw new SQLTransientConnectionException("DataSource returned null unexpectedly");
+ }
+
+ setupConnection(connection);
+ lastConnectionFailure.set(null);
+ return connection;
+ }
+ catch (Exception e) {
+ if (connection != null) {
+ quietlyCloseConnection(connection, "(Failed to create/setup connection)");
+ }
+ else if (getLastConnectionFailure() == null) {
+ LOGGER.debug("{} - Failed to create/setup connection: {}", poolName, e.getMessage());
+ }
+
+ lastConnectionFailure.set(e);
+ throw e;
+ }
+ finally {
+ // tracker will be null during failFast check
+ if (metricsTracker != null) {
+ metricsTracker.recordConnectionCreated(elapsedMillis(start));
+ }
+ }
+ }
+
+ /**
+ * Setup a connection initial state.
+ *
+ * @param connection a Connection
+ * @throws SQLException thrown from driver
+ */
+ private void setupConnection(final Connection connection) throws ConnectionSetupException
+ {
+ try {
+ if (networkTimeout == UNINITIALIZED) {
+ networkTimeout = getAndSetNetworkTimeout(connection, validationTimeout);
+ }
+ else {
+ setNetworkTimeout(connection, validationTimeout);
+ }
+
+ connection.setReadOnly(isReadOnly);
+ connection.setAutoCommit(isAutoCommit);
+
+ checkDriverSupport(connection);
+
+ if (transactionIsolation != defaultTransactionIsolation) {
+ connection.setTransactionIsolation(transactionIsolation);
+ }
+
+ if (catalog != null) {
+ connection.setCatalog(catalog);
+ }
+
+ executeSql(connection, config.getConnectionInitSql(), true);
+
+ setNetworkTimeout(connection, networkTimeout);
+ }
+ catch (SQLException e) {
+ throw new ConnectionSetupException(e);
+ }
+ }
+
+ /**
+ * Execute isValid() or connection test query.
+ *
+ * @param connection a Connection to check
+ */
+ private void checkDriverSupport(final Connection connection) throws SQLException
+ {
+ if (!isValidChecked) {
+ try {
+ if (isUseJdbc4Validation) {
+ connection.isValid(1);
+ }
+ else {
+ executeSql(connection, config.getConnectionTestQuery(), false);
+ }
+ }
+ catch (Throwable e) {
+ LOGGER.error("{} - Failed to execute" + (isUseJdbc4Validation ? " isValid() for connection, configure" : "") + " connection test query ({}).", poolName, e.getMessage());
+ throw e;
+ }
+
+ try {
+ defaultTransactionIsolation = connection.getTransactionIsolation();
+ if (transactionIsolation == -1) {
+ transactionIsolation = defaultTransactionIsolation;
+ }
+ }
+ catch (SQLException e) {
+ LOGGER.warn("{} - Default transaction isolation level detection failed ({}).", poolName, e.getMessage());
+ }
+ finally {
+ isValidChecked = true;
+ }
+ }
+ }
+
+ /**
+ * Set the query timeout, if it is supported by the driver.
+ *
+ * @param statement a statement to set the query timeout on
+ * @param timeoutSec the number of seconds before timeout
+ */
+ private void setQueryTimeout(final Statement statement, final int timeoutSec)
+ {
+ if (isQueryTimeoutSupported != FALSE) {
+ try {
+ statement.setQueryTimeout(timeoutSec);
+ isQueryTimeoutSupported = TRUE;
+ }
+ catch (Throwable e) {
+ if (isQueryTimeoutSupported == UNINITIALIZED) {
+ isQueryTimeoutSupported = FALSE;
+ LOGGER.info("{} - Failed to set query timeout for statement. ({})", poolName, e.getMessage());
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the network timeout, if <code>isUseNetworkTimeout</code> is <code>true</code> and the
+ * driver supports it. Return the pre-existing value of the network timeout.
+ *
+ * @param connection the connection to set the network timeout on
+ * @param timeoutMs the number of milliseconds before timeout
+ * @return the pre-existing network timeout value
+ */
+ private int getAndSetNetworkTimeout(final Connection connection, final long timeoutMs)
+ {
+ if (isNetworkTimeoutSupported != FALSE) {
+ try {
+ final int originalTimeout = connection.getNetworkTimeout();
+ connection.setNetworkTimeout(netTimeoutExecutor, (int) timeoutMs);
+ isNetworkTimeoutSupported = TRUE;
+ return originalTimeout;
+ }
+ catch (Throwable e) {
+ if (isNetworkTimeoutSupported == UNINITIALIZED) {
+ isNetworkTimeoutSupported = FALSE;
+
+ LOGGER.info("{} - Driver does not support get/set network timeout for connections. ({})", poolName, e.getMessage());
+ if (validationTimeout < SECONDS.toMillis(1)) {
+ LOGGER.warn("{} - A validationTimeout of less than 1 second cannot be honored on drivers without setNetworkTimeout() support.", poolName);
+ }
+ else if (validationTimeout % SECONDS.toMillis(1) != 0) {
+ LOGGER.warn("{} - A validationTimeout with fractional second granularity cannot be honored on drivers without setNetworkTimeout() support.", poolName);
+ }
+ }
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Set the network timeout, if <code>isUseNetworkTimeout</code> is <code>true</code> and the
+ * driver supports it.
+ *
+ * @param connection the connection to set the network timeout on
+ * @param timeoutMs the number of milliseconds before timeout
+ * @throws SQLException throw if the connection.setNetworkTimeout() call throws
+ */
+ private void setNetworkTimeout(final Connection connection, final long timeoutMs) throws SQLException
+ {
+ if (isNetworkTimeoutSupported == TRUE) {
+ connection.setNetworkTimeout(netTimeoutExecutor, (int) timeoutMs);
+ }
+ }
+
+ /**
+ * Execute the user-specified init SQL.
+ *
+ * @param connection the connection to initialize
+ * @param sql the SQL to execute
+ * @param isCommit whether to commit the SQL after execution or not
+ * @throws SQLException throws if the init SQL execution fails
+ */
+ private void executeSql(final Connection connection, final String sql, final boolean isCommit) throws SQLException
+ {
+ if (sql != null) {
+ try (Statement statement = connection.createStatement()) {
+ // connection was created a few milliseconds before, so set query timeout is omitted (we assume it will succeed)
+ statement.execute(sql);
+ }
+
+ if (isIsolateInternalQueries && !isAutoCommit) {
+ if (isCommit) {
+ connection.commit();
+ }
+ else {
+ connection.rollback();
+ }
+ }
+ }
+ }
+
+ private void createNetworkTimeoutExecutor(final DataSource dataSource, final String dsClassName, final String jdbcUrl)
+ {
+ // Temporary hack for MySQL issue: http://bugs.mysql.com/bug.php?id=75615
+ if ((dsClassName != null && dsClassName.contains("Mysql")) ||
+ (jdbcUrl != null && jdbcUrl.contains("mysql")) ||
+ (dataSource != null && dataSource.getClass().getName().contains("Mysql"))) {
+ netTimeoutExecutor = new SynchronousExecutor();
+ }
+ else {
+ ThreadFactory threadFactory = config.getThreadFactory();
+ threadFactory = threadFactory != null ? threadFactory : new DefaultThreadFactory(poolName + " network timeout executor", true);
+ ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool(threadFactory);
+ executor.setKeepAliveTime(15, SECONDS);
+ executor.allowCoreThreadTimeOut(true);
+ netTimeoutExecutor = executor;
+ }
+ }
+
+ /**
+ * Set the loginTimeout on the specified DataSource.
+ *
+ * @param dataSource the DataSource
+ */
+ private void setLoginTimeout(final DataSource dataSource)
+ {
+ if (connectionTimeout != Integer.MAX_VALUE) {
+ try {
+ dataSource.setLoginTimeout(Math.max(1, (int) MILLISECONDS.toSeconds(500L + connectionTimeout)));
+ }
+ catch (Throwable e) {
+ LOGGER.info("{} - Failed to set login timeout for data source. ({})", poolName, e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * This will create a string for debug logging. Given a set of "reset bits", this
+ * method will return a concatenated string, for example:
+ *
+ * Input : 0b00110
+ * Output: "autoCommit, isolation"
+ *
+ * @param bits a set of "reset bits"
+ * @return a string of which states were reset
+ */
+ private String stringFromResetBits(final int bits)
+ {
+ final StringBuilder sb = new StringBuilder();
+ for (int ndx = 0; ndx < RESET_STATES.length; ndx++) {
+ if ( (bits & (0b1 << ndx)) != 0) {
+ sb.append(RESET_STATES[ndx]).append(", ");
+ }
+ }
+
+ sb.setLength(sb.length() - 2); // trim trailing comma
+ return sb.toString();
+ }
+
+ // ***********************************************************************
+ // Private Static Classes
+ // ***********************************************************************
+
+ static class ConnectionSetupException extends Exception
+ {
+ private static final long serialVersionUID = 929872118275916521L;
+
+ public ConnectionSetupException(Throwable t)
+ {
+ super(t);
+ }
+ }
+
+ /**
+ * Special executor used only to work around a MySQL issue that has not been addressed.
+ * MySQL issue: http://bugs.mysql.com/bug.php?id=75615
+ */
+ private static class SynchronousExecutor implements Executor
+ {
+ /** {@inheritDoc} */
+ @Override
+ public void execute(Runnable command)
+ {
+ try {
+ command.run();
+ }
+ catch (Throwable t) {
+ LoggerFactory.getLogger(PoolBase.class).debug("Failed to execute: {}", command, t);
+ }
+ }
+ }
+
+ static interface IMetricsTrackerDelegate extends AutoCloseable
+ {
+ default void recordConnectionUsage(PoolEntry poolEntry) {}
+
+ default void recordConnectionCreated(long connectionCreatedMillis) {}
+
+ default void recordBorrowStats(final PoolEntry poolEntry, final long startTime) {}
+
+ default void recordConnectionTimeout() {}
+
+ @Override
+ default void close() {}
+ }
+
+ /**
+ * A class that delegates to a MetricsTracker implementation. The use of a delegate
+ * allows us to use the NopMetricsTrackerDelegate when metrics are disabled, which in
+ * turn allows the JIT to completely optimize away to callsites to record metrics.
+ */
+ static class MetricsTrackerDelegate implements IMetricsTrackerDelegate
+ {
+ final IMetricsTracker tracker;
+
+ MetricsTrackerDelegate(IMetricsTracker tracker)
+ {
+ this.tracker = tracker;
+ }
+
+ @Override
+ public void recordConnectionUsage(final PoolEntry poolEntry)
+ {
+ tracker.recordConnectionUsageMillis(poolEntry.getMillisSinceBorrowed());
+ }
+
+ @Override
+ public void recordConnectionCreated(long connectionCreatedMillis)
+ {
+ tracker.recordConnectionCreatedMillis(connectionCreatedMillis);
+ }
+
+ @Override
+ public void recordBorrowStats(final PoolEntry poolEntry, final long startTime)
+ {
+ final long now = currentTime();
+ poolEntry.lastBorrowed = now;
+ tracker.recordConnectionAcquiredNanos(elapsedNanos(startTime, now));
+ }
+
+ @Override
+ public void recordConnectionTimeout() {
+ tracker.recordConnectionTimeout();
+ }
+
+ @Override
+ public void close()
+ {
+ tracker.close();
+ }
+ }
+
+ /**
+ * A no-op implementation of the IMetricsTrackerDelegate that is used when metrics capture is
+ * disabled.
+ */
+ static final class NopMetricsTrackerDelegate implements IMetricsTrackerDelegate {}
+}
diff --git a/src/main/java/com/zaxxer/hikari/pool/PoolEntry.java b/src/main/java/com/zaxxer/hikari/pool/PoolEntry.java
new file mode 100644
index 0000000..5b16047
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/pool/PoolEntry.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.util.ClockSource.currentTime;
+import static com.zaxxer.hikari.util.ClockSource.elapsedDisplayString;
+import static com.zaxxer.hikari.util.ClockSource.elapsedMillis;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Comparator;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry;
+import com.zaxxer.hikari.util.FastList;
+
+/**
+ * Entry used in the ConcurrentBag to track Connection instances.
+ *
+ * @author Brett Wooldridge
+ */
+final class PoolEntry implements IConcurrentBagEntry
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(PoolEntry.class);
+ private static final AtomicIntegerFieldUpdater<PoolEntry> stateUpdater;
+
+ static final Comparator<PoolEntry> LASTACCESS_REVERSE_COMPARABLE;
+
+ Connection connection;
+ long lastAccessed;
+ long lastBorrowed;
+ private volatile int state;
+ private volatile boolean evict;
+
+ private volatile ScheduledFuture<?> endOfLife;
+
+ private final FastList<Statement> openStatements;
+ private final HikariPool hikariPool;
+
+ private final boolean isReadOnly;
+ private final boolean isAutoCommit;
+
+ static
+ {
+ LASTACCESS_REVERSE_COMPARABLE = new Comparator<PoolEntry>() {
+ @Override
+ public int compare(final PoolEntry entryOne, final PoolEntry entryTwo) {
+ return Long.compare(entryTwo.lastAccessed, entryOne.lastAccessed);
+ }
+ };
+
+ stateUpdater = AtomicIntegerFieldUpdater.newUpdater(PoolEntry.class, "state");
+ }
+
+ PoolEntry(final Connection connection, final PoolBase pool, final boolean isReadOnly, final boolean isAutoCommit)
+ {
+ this.connection = connection;
+ this.hikariPool = (HikariPool) pool;
+ this.isReadOnly = isReadOnly;
+ this.isAutoCommit = isAutoCommit;
+ this.lastAccessed = currentTime();
+ this.openStatements = new FastList<>(Statement.class, 16);
+ }
+
+ /**
+ * Release this entry back to the pool.
+ *
+ * @param lastAccessed last access time-stamp
+ */
+ void recycle(final long lastAccessed)
+ {
+ if (connection != null) {
+ this.lastAccessed = lastAccessed;
+ hikariPool.recycle(this);
+ }
+ }
+
+ /**
+ * @param endOfLife
+ */
+ void setFutureEol(final ScheduledFuture<?> endOfLife)
+ {
+ this.endOfLife = endOfLife;
+ }
+
+ Connection createProxyConnection(final ProxyLeakTask leakTask, final long now)
+ {
+ return ProxyFactory.getProxyConnection(this, connection, openStatements, leakTask, now, isReadOnly, isAutoCommit);
+ }
+
+ void resetConnectionState(final ProxyConnection proxyConnection, final int dirtyBits) throws SQLException
+ {
+ hikariPool.resetConnectionState(connection, proxyConnection, dirtyBits);
+ }
+
+ String getPoolName()
+ {
+ return hikariPool.toString();
+ }
+
+ boolean isMarkedEvicted()
+ {
+ return evict;
+ }
+
+ void markEvicted()
+ {
+ this.evict = true;
+ }
+
+ void evict(final String closureReason)
+ {
+ hikariPool.closeConnection(this, closureReason);
+ }
+
+ /** Returns millis since lastBorrowed */
+ long getMillisSinceBorrowed()
+ {
+ return elapsedMillis(lastBorrowed);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString()
+ {
+ final long now = currentTime();
+ return connection
+ + ", accessed " + elapsedDisplayString(lastAccessed, now) + " ago, "
+ + stateToString();
+ }
+
+ // ***********************************************************************
+ // IConcurrentBagEntry methods
+ // ***********************************************************************
+
+ /** {@inheritDoc} */
+ @Override
+ public int getState()
+ {
+ return stateUpdater.get(this);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean compareAndSet(int expect, int update)
+ {
+ return stateUpdater.compareAndSet(this, expect, update);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setState(int update)
+ {
+ stateUpdater.set(this, update);
+ }
+
+ Connection close()
+ {
+ ScheduledFuture<?> eol = endOfLife;
+ if (eol != null && !eol.isDone() && !eol.cancel(false)) {
+ LOGGER.warn("{} - maxLifeTime expiration task cancellation unexpectedly returned false for connection {}", getPoolName(), connection);
+ }
+
+ Connection con = connection;
+ connection = null;
+ endOfLife = null;
+ return con;
+ }
+
+ private String stateToString()
+ {
+ switch (state) {
+ case STATE_IN_USE:
+ return "IN_USE";
+ case STATE_NOT_IN_USE:
+ return "NOT_IN_USE";
+ case STATE_REMOVED:
+ return "REMOVED";
+ case STATE_RESERVED:
+ return "RESERVED";
+ default:
+ return "Invalid";
+ }
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyCallableStatement.java b/src/main/java/com/zaxxer/hikari/pool/ProxyCallableStatement.java
new file mode 100644
index 0000000..922be53
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyCallableStatement.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.pool;
+
+import java.sql.CallableStatement;
+
+/**
+ * This is the proxy class for java.sql.CallableStatement.
+ *
+ * @author Brett Wooldridge
+ */
+public abstract class ProxyCallableStatement extends ProxyPreparedStatement implements CallableStatement
+{
+ protected ProxyCallableStatement(ProxyConnection connection, CallableStatement statement)
+ {
+ super(connection, statement);
+ }
+
+ // **********************************************************************
+ // Overridden java.sql.CallableStatement Methods
+ // **********************************************************************
+
+} \ No newline at end of file
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java b/src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java
new file mode 100644
index 0000000..7143bae
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.util.ClockSource.currentTime;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.sql.Wrapper;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.zaxxer.hikari.util.FastList;
+
+/**
+ * This is the proxy class for java.sql.Connection.
+ *
+ * @author Brett Wooldridge
+ */
+public abstract class ProxyConnection implements Connection
+{
+ static final int DIRTY_BIT_READONLY = 0b00001;
+ static final int DIRTY_BIT_AUTOCOMMIT = 0b00010;
+ static final int DIRTY_BIT_ISOLATION = 0b00100;
+ static final int DIRTY_BIT_CATALOG = 0b01000;
+ static final int DIRTY_BIT_NETTIMEOUT = 0b10000;
+
+ private static final Logger LOGGER;
+ private static final Set<String> ERROR_STATES;
+ private static final Set<Integer> ERROR_CODES;
+
+ protected Connection delegate;
+
+ private final PoolEntry poolEntry;
+ private final ProxyLeakTask leakTask;
+ private final FastList<Statement> openStatements;
+
+ private int dirtyBits;
+ private long lastAccess;
+ private boolean isCommitStateDirty;
+
+ private boolean isReadOnly;
+ private boolean isAutoCommit;
+ private int networkTimeout;
+ private int transactionIsolation;
+ private String dbcatalog;
+
+ // static initializer
+ static {
+ LOGGER = LoggerFactory.getLogger(ProxyConnection.class);
+
+ ERROR_STATES = new HashSet<>();
+ ERROR_STATES.add("57P01"); // ADMIN SHUTDOWN
+ ERROR_STATES.add("57P02"); // CRASH SHUTDOWN
+ ERROR_STATES.add("57P03"); // CANNOT CONNECT NOW
+ ERROR_STATES.add("01002"); // SQL92 disconnect error
+ ERROR_STATES.add("JZ0C0"); // Sybase disconnect error
+ ERROR_STATES.add("JZ0C1"); // Sybase disconnect error
+
+ ERROR_CODES = new HashSet<>();
+ ERROR_CODES.add(500150);
+ ERROR_CODES.add(2399);
+ }
+
+ protected ProxyConnection(final PoolEntry poolEntry, final Connection connection, final FastList<Statement> openStatements, final ProxyLeakTask leakTask, final long now, final boolean isReadOnly, final boolean isAutoCommit) {
+ this.poolEntry = poolEntry;
+ this.delegate = connection;
+ this.openStatements = openStatements;
+ this.leakTask = leakTask;
+ this.lastAccess = now;
+ this.isReadOnly = isReadOnly;
+ this.isAutoCommit = isAutoCommit;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final String toString()
+ {
+ return new StringBuilder(64)
+ .append(this.getClass().getSimpleName()).append('@').append(System.identityHashCode(this))
+ .append(" wrapping ")
+ .append(delegate).toString();
+ }
+
+ // ***********************************************************************
+ // Connection State Accessors
+ // ***********************************************************************
+
+ final boolean getAutoCommitState()
+ {
+ return isAutoCommit;
+ }
+
+ final String getCatalogState()
+ {
+ return dbcatalog;
+ }
+
+ final int getTransactionIsolationState()
+ {
+ return transactionIsolation;
+ }
+
+ final boolean getReadOnlyState()
+ {
+ return isReadOnly;
+ }
+
+ final int getNetworkTimeoutState()
+ {
+ return networkTimeout;
+ }
+
+ // ***********************************************************************
+ // Internal methods
+ // ***********************************************************************
+
+ final PoolEntry getPoolEntry()
+ {
+ return poolEntry;
+ }
+
+ final SQLException checkException(SQLException sqle)
+ {
+ SQLException nse = sqle;
+ for (int depth = 0; delegate != ClosedConnection.CLOSED_CONNECTION && nse != null && depth < 10; depth++) {
+ final String sqlState = nse.getSQLState();
+ if (sqlState != null && sqlState.startsWith("08") || ERROR_STATES.contains(sqlState) || ERROR_CODES.contains(nse.getErrorCode())) {
+ // broken connection
+ LOGGER.warn("{} - Connection {} marked as broken because of SQLSTATE({}), ErrorCode({})",
+ poolEntry.getPoolName(), delegate, sqlState, nse.getErrorCode(), nse);
+ leakTask.cancel();
+ poolEntry.evict("(connection is broken)");
+ delegate = ClosedConnection.CLOSED_CONNECTION;
+ }
+ else {
+ nse = nse.getNextException();
+ }
+ }
+
+ return sqle;
+ }
+
+ final synchronized void untrackStatement(final Statement statement)
+ {
+ openStatements.remove(statement);
+ }
+
+ final void markCommitStateDirty()
+ {
+ if (isAutoCommit) {
+ lastAccess = currentTime();
+ }
+ else {
+ isCommitStateDirty = true;
+ }
+ }
+
+ void cancelLeakTask()
+ {
+ leakTask.cancel();
+ }
+
+ private final synchronized <T extends Statement> T trackStatement(final T statement)
+ {
+ openStatements.add(statement);
+
+ return statement;
+ }
+
+ private final void closeStatements()
+ {
+ final int size = openStatements.size();
+ if (size > 0) {
+ for (int i = 0; i < size && delegate != ClosedConnection.CLOSED_CONNECTION; i++) {
+ try (Statement statement = openStatements.get(i)) {
+ // automatic resource cleanup
+ }
+ catch (SQLException e) {
+ checkException(e);
+ }
+ }
+
+ synchronized (this) {
+ openStatements.clear();
+ }
+ }
+ }
+
+ // **********************************************************************
+ // "Overridden" java.sql.Connection Methods
+ // **********************************************************************
+
+ /** {@inheritDoc} */
+ @Override
+ public final void close() throws SQLException
+ {
+ // Closing statements can cause connection eviction, so this must run before the conditional below
+ closeStatements();
+
+ if (delegate != ClosedConnection.CLOSED_CONNECTION) {
+ leakTask.cancel();
+
+ try {
+ if (isCommitStateDirty && !isAutoCommit) {
+ delegate.rollback();
+ lastAccess = currentTime();
+ LOGGER.debug("{} - Executed rollback on connection {} due to dirty commit state on close().", poolEntry.getPoolName(), delegate);
+ }
+
+ if (dirtyBits != 0) {
+ poolEntry.resetConnectionState(this, dirtyBits);
+ lastAccess = currentTime();
+ }
+
+ delegate.clearWarnings();
+ }
+ catch (SQLException e) {
+ // when connections are aborted, exceptions are often thrown that should not reach the application
+ if (!poolEntry.isMarkedEvicted()) {
+ throw checkException(e);
+ }
+ }
+ finally {
+ delegate = ClosedConnection.CLOSED_CONNECTION;
+ poolEntry.recycle(lastAccess);
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isClosed() throws SQLException
+ {
+ return (delegate == ClosedConnection.CLOSED_CONNECTION);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Statement createStatement() throws SQLException
+ {
+ return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement()));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Statement createStatement(int resultSetType, int concurrency) throws SQLException
+ {
+ return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement(resultSetType, concurrency)));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Statement createStatement(int resultSetType, int concurrency, int holdability) throws SQLException
+ {
+ return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement(resultSetType, concurrency, holdability)));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public CallableStatement prepareCall(String sql) throws SQLException
+ {
+ return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql)));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public CallableStatement prepareCall(String sql, int resultSetType, int concurrency) throws SQLException
+ {
+ return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql, resultSetType, concurrency)));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public CallableStatement prepareCall(String sql, int resultSetType, int concurrency, int holdability) throws SQLException
+ {
+ return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql, resultSetType, concurrency, holdability)));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public PreparedStatement prepareStatement(String sql) throws SQLException
+ {
+ return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql)));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException
+ {
+ return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, autoGeneratedKeys)));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public PreparedStatement prepareStatement(String sql, int resultSetType, int concurrency) throws SQLException
+ {
+ return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, resultSetType, concurrency)));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public PreparedStatement prepareStatement(String sql, int resultSetType, int concurrency, int holdability) throws SQLException
+ {
+ return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, resultSetType, concurrency, holdability)));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException
+ {
+ return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, columnIndexes)));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException
+ {
+ return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, columnNames)));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void commit() throws SQLException
+ {
+ delegate.commit();
+ isCommitStateDirty = false;
+ lastAccess = currentTime();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void rollback() throws SQLException
+ {
+ delegate.rollback();
+ isCommitStateDirty = false;
+ lastAccess = currentTime();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void rollback(Savepoint savepoint) throws SQLException
+ {
+ delegate.rollback(savepoint);
+ isCommitStateDirty = false;
+ lastAccess = currentTime();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setAutoCommit(boolean autoCommit) throws SQLException
+ {
+ delegate.setAutoCommit(autoCommit);
+ isAutoCommit = autoCommit;
+ dirtyBits |= DIRTY_BIT_AUTOCOMMIT;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setReadOnly(boolean readOnly) throws SQLException
+ {
+ delegate.setReadOnly(readOnly);
+ isReadOnly = readOnly;
+ isCommitStateDirty = false;
+ dirtyBits |= DIRTY_BIT_READONLY;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setTransactionIsolation(int level) throws SQLException
+ {
+ delegate.setTransactionIsolation(level);
+ transactionIsolation = level;
+ dirtyBits |= DIRTY_BIT_ISOLATION;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setCatalog(String catalog) throws SQLException
+ {
+ delegate.setCatalog(catalog);
+ dbcatalog = catalog;
+ dirtyBits |= DIRTY_BIT_CATALOG;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException
+ {
+ delegate.setNetworkTimeout(executor, milliseconds);
+ networkTimeout = milliseconds;
+ dirtyBits |= DIRTY_BIT_NETTIMEOUT;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final boolean isWrapperFor(Class<?> iface) throws SQLException
+ {
+ return iface.isInstance(delegate) || (delegate instanceof Wrapper && delegate.isWrapperFor(iface));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @SuppressWarnings("unchecked")
+ public final <T> T unwrap(Class<T> iface) throws SQLException
+ {
+ if (iface.isInstance(delegate)) {
+ return (T) delegate;
+ }
+ else if (delegate instanceof Wrapper) {
+ return delegate.unwrap(iface);
+ }
+
+ throw new SQLException("Wrapped connection is not an instance of " + iface);
+ }
+
+ // **********************************************************************
+ // Private classes
+ // **********************************************************************
+
+ private static final class ClosedConnection
+ {
+ static final Connection CLOSED_CONNECTION = getClosedConnection();
+
+ private static Connection getClosedConnection()
+ {
+ InvocationHandler handler = new InvocationHandler() {
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
+ {
+ final String methodName = method.getName();
+ if ("abort".equals(methodName)) {
+ return Void.TYPE;
+ }
+ else if ("isValid".equals(methodName)) {
+ return Boolean.FALSE;
+ }
+ else if ("toString".equals(methodName)) {
+ return ClosedConnection.class.getCanonicalName();
+ }
+
+ throw new SQLException("Connection is closed");
+ }
+ };
+
+ return (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), new Class[] { Connection.class }, handler);
+ }
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyFactory.java b/src/main/java/com/zaxxer/hikari/pool/ProxyFactory.java
new file mode 100644
index 0000000..026debb
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyFactory.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.pool;
+
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.Statement;
+
+import com.zaxxer.hikari.util.FastList;
+
+/**
+ * A factory class that produces proxies around instances of the standard
+ * JDBC interfaces.
+ *
+ * @author Brett Wooldridge
+ */
+public final class ProxyFactory
+{
+ private ProxyFactory()
+ {
+ // unconstructable
+ }
+
+ /**
+ * Create a proxy for the specified {@link Connection} instance.
+ * @param poolEntry
+ * @param connection
+ * @param openStatements
+ * @param leakTask
+ * @param now
+ * @param isReadOnly
+ * @param isAutoCommit
+ * @return a proxy that wraps the specified {@link Connection}
+ */
+ static ProxyConnection getProxyConnection(final PoolEntry poolEntry, final Connection connection, final FastList<Statement> openStatements, final ProxyLeakTask leakTask, final long now, final boolean isReadOnly, final boolean isAutoCommit)
+ {
+ // Body is replaced (injected) by JavassistProxyFactory
+ throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");
+ }
+
+ static Statement getProxyStatement(final ProxyConnection connection, final Statement statement)
+ {
+ // Body is replaced (injected) by JavassistProxyFactory
+ throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");
+ }
+
+ static CallableStatement getProxyCallableStatement(final ProxyConnection connection, final CallableStatement statement)
+ {
+ // Body is replaced (injected) by JavassistProxyFactory
+ throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");
+ }
+
+ static PreparedStatement getProxyPreparedStatement(final ProxyConnection connection, final PreparedStatement statement)
+ {
+ // Body is replaced (injected) by JavassistProxyFactory
+ throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");
+ }
+
+ static ResultSet getProxyResultSet(final ProxyConnection connection, final ProxyStatement statement, final ResultSet resultSet)
+ {
+ // Body is replaced (injected) by JavassistProxyFactory
+ throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyLeakTask.java b/src/main/java/com/zaxxer/hikari/pool/ProxyLeakTask.java
new file mode 100644
index 0000000..0fdc93e
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyLeakTask.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.pool;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A Runnable that is scheduled in the future to report leaks. The ScheduledFuture is
+ * cancelled if the connection is closed before the leak time expires.
+ *
+ * @author Brett Wooldridge
+ */
+class ProxyLeakTask implements Runnable
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(ProxyLeakTask.class);
+ private static final ProxyLeakTask NO_LEAK;
+
+ private ScheduledExecutorService executorService;
+ private long leakDetectionThreshold;
+ private ScheduledFuture<?> scheduledFuture;
+ private String connectionName;
+ private Exception exception;
+ private boolean isLeaked;
+
+ static
+ {
+ NO_LEAK = new ProxyLeakTask() {
+ @Override
+ public void cancel() {}
+ };
+ }
+
+ ProxyLeakTask(final long leakDetectionThreshold, final ScheduledExecutorService executorService)
+ {
+ this.executorService = executorService;
+ this.leakDetectionThreshold = leakDetectionThreshold;
+ }
+
+ private ProxyLeakTask(final ProxyLeakTask parent, final PoolEntry poolEntry)
+ {
+ this.exception = new Exception("Apparent connection leak detected");
+ this.connectionName = poolEntry.connection.toString();
+ scheduledFuture = parent.executorService.schedule(this, parent.leakDetectionThreshold, TimeUnit.MILLISECONDS);
+ }
+
+ private ProxyLeakTask()
+ {
+ }
+
+ ProxyLeakTask schedule(final PoolEntry bagEntry)
+ {
+ return (leakDetectionThreshold == 0) ? NO_LEAK : new ProxyLeakTask(this, bagEntry);
+ }
+
+ void updateLeakDetectionThreshold(final long leakDetectionThreshold)
+ {
+ this.leakDetectionThreshold = leakDetectionThreshold;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void run()
+ {
+ isLeaked = true;
+
+ final StackTraceElement[] stackTrace = exception.getStackTrace();
+ final StackTraceElement[] trace = new StackTraceElement[stackTrace.length - 5];
+ System.arraycopy(stackTrace, 5, trace, 0, trace.length);
+
+ exception.setStackTrace(trace);
+ LOGGER.warn("Connection leak detection triggered for {}, stack trace follows", connectionName, exception);
+ }
+
+ void cancel()
+ {
+ scheduledFuture.cancel(false);
+ if (isLeaked) {
+ LOGGER.info("Previously reported leaked connection {} was returned to the pool (unleaked)", connectionName);
+ }
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyPreparedStatement.java b/src/main/java/com/zaxxer/hikari/pool/ProxyPreparedStatement.java
new file mode 100644
index 0000000..e2d96c9
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyPreparedStatement.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.pool;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * This is the proxy class for java.sql.PreparedStatement.
+ *
+ * @author Brett Wooldridge
+ */
+public abstract class ProxyPreparedStatement extends ProxyStatement implements PreparedStatement
+{
+ protected ProxyPreparedStatement(ProxyConnection connection, PreparedStatement statement)
+ {
+ super(connection, statement);
+ }
+
+ // **********************************************************************
+ // Overridden java.sql.PreparedStatement Methods
+ // **********************************************************************
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean execute() throws SQLException
+ {
+ connection.markCommitStateDirty();
+ return ((PreparedStatement) delegate).execute();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ResultSet executeQuery() throws SQLException
+ {
+ connection.markCommitStateDirty();
+ ResultSet resultSet = ((PreparedStatement) delegate).executeQuery();
+ return ProxyFactory.getProxyResultSet(connection, this, resultSet);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int executeUpdate() throws SQLException
+ {
+ connection.markCommitStateDirty();
+ return ((PreparedStatement) delegate).executeUpdate();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long executeLargeUpdate() throws SQLException
+ {
+ connection.markCommitStateDirty();
+ return ((PreparedStatement) delegate).executeLargeUpdate();
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyResultSet.java b/src/main/java/com/zaxxer/hikari/pool/ProxyResultSet.java
new file mode 100644
index 0000000..1933979
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyResultSet.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.pool;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Wrapper;
+
+/**
+ * This is the proxy class for java.sql.ResultSet.
+ *
+ * @author Brett Wooldridge
+ */
+public abstract class ProxyResultSet implements ResultSet
+{
+ protected final ProxyConnection connection;
+ protected final ProxyStatement statement;
+ protected final ResultSet delegate;
+
+ protected ProxyResultSet(ProxyConnection connection, ProxyStatement statement, ResultSet resultSet)
+ {
+ this.connection = connection;
+ this.statement = statement;
+ this.delegate = resultSet;
+ }
+
+ final SQLException checkException(SQLException e)
+ {
+ return connection.checkException(e);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString()
+ {
+ return new StringBuilder(64)
+ .append(this.getClass().getSimpleName()).append('@').append(System.identityHashCode(this))
+ .append(" wrapping ")
+ .append(delegate).toString();
+ }
+
+ // **********************************************************************
+ // Overridden java.sql.ResultSet Methods
+ // **********************************************************************
+
+ /** {@inheritDoc} */
+ @Override
+ public final Statement getStatement() throws SQLException
+ {
+ return statement;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateRow() throws SQLException
+ {
+ connection.markCommitStateDirty();
+ delegate.updateRow();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void insertRow() throws SQLException
+ {
+ connection.markCommitStateDirty();
+ delegate.insertRow();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void deleteRow() throws SQLException
+ {
+ connection.markCommitStateDirty();
+ delegate.deleteRow();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @SuppressWarnings("unchecked")
+ public final <T> T unwrap(Class<T> iface) throws SQLException
+ {
+ if (iface.isInstance(delegate)) {
+ return (T) delegate;
+ }
+ else if (delegate instanceof Wrapper) {
+ return delegate.unwrap(iface);
+ }
+
+ throw new SQLException("Wrapped ResultSet is not an instance of " + iface);
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyStatement.java b/src/main/java/com/zaxxer/hikari/pool/ProxyStatement.java
new file mode 100644
index 0000000..1d92cd8
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyStatement.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.pool;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Wrapper;
+
+/**
+ * This is the proxy class for java.sql.Statement.
+ *
+ * @author Brett Wooldridge
+ */
+public abstract class ProxyStatement implements Statement
+{
+ protected final ProxyConnection connection;
+ protected final Statement delegate;
+
+ private boolean isClosed;
+ private ResultSet proxyResultSet;
+
+ protected ProxyStatement(ProxyConnection connection, Statement statement)
+ {
+ this.connection = connection;
+ this.delegate = statement;
+ }
+
+ final SQLException checkException(SQLException e)
+ {
+ return connection.checkException(e);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final String toString()
+ {
+ final String delegateToString = delegate.toString();
+ return new StringBuilder(64 + delegateToString.length())
+ .append(this.getClass().getSimpleName()).append('@').append(System.identityHashCode(this))
+ .append(" wrapping ")
+ .append(delegateToString).toString();
+ }
+
+ // **********************************************************************
+ // Overridden java.sql.Statement Methods
+ // **********************************************************************
+
+ /** {@inheritDoc} */
+ @Override
+ public final void close() throws SQLException
+ {
+ if (isClosed) {
+ return;
+ }
+
+ isClosed = true;
+ connection.untrackStatement(delegate);
+
+ try {
+ delegate.close();
+ }
+ catch (SQLException e) {
+ throw connection.checkException(e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Connection getConnection() throws SQLException
+ {
+ return connection;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean execute(String sql) throws SQLException
+ {
+ connection.markCommitStateDirty();
+ return delegate.execute(sql);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean execute(String sql, int autoGeneratedKeys) throws SQLException
+ {
+ connection.markCommitStateDirty();
+ return delegate.execute(sql, autoGeneratedKeys);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ResultSet executeQuery(String sql) throws SQLException
+ {
+ connection.markCommitStateDirty();
+ ResultSet resultSet = delegate.executeQuery(sql);
+ return ProxyFactory.getProxyResultSet(connection, this, resultSet);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int executeUpdate(String sql) throws SQLException
+ {
+ connection.markCommitStateDirty();
+ return delegate.executeUpdate(sql);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int[] executeBatch() throws SQLException
+ {
+ connection.markCommitStateDirty();
+ return delegate.executeBatch();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException
+ {
+ connection.markCommitStateDirty();
+ return delegate.executeUpdate(sql, autoGeneratedKeys);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int executeUpdate(String sql, int[] columnIndexes) throws SQLException
+ {
+ connection.markCommitStateDirty();
+ return delegate.executeUpdate(sql, columnIndexes);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int executeUpdate(String sql, String[] columnNames) throws SQLException
+ {
+ connection.markCommitStateDirty();
+ return delegate.executeUpdate(sql, columnNames);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean execute(String sql, int[] columnIndexes) throws SQLException
+ {
+ connection.markCommitStateDirty();
+ return delegate.execute(sql, columnIndexes);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean execute(String sql, String[] columnNames) throws SQLException
+ {
+ connection.markCommitStateDirty();
+ return delegate.execute(sql, columnNames);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long[] executeLargeBatch() throws SQLException
+ {
+ connection.markCommitStateDirty();
+ return delegate.executeLargeBatch();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long executeLargeUpdate(String sql) throws SQLException
+ {
+ connection.markCommitStateDirty();
+ return delegate.executeLargeUpdate(sql);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException
+ {
+ connection.markCommitStateDirty();
+ return delegate.executeLargeUpdate(sql, autoGeneratedKeys);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException
+ {
+ connection.markCommitStateDirty();
+ return delegate.executeLargeUpdate(sql, columnIndexes);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException
+ {
+ connection.markCommitStateDirty();
+ return delegate.executeLargeUpdate(sql, columnNames);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ResultSet getResultSet() throws SQLException {
+ final ResultSet resultSet = delegate.getResultSet();
+ if (resultSet != null) {
+ if (proxyResultSet == null || ((ProxyResultSet) proxyResultSet).delegate != resultSet) {
+ proxyResultSet = ProxyFactory.getProxyResultSet(connection, this, resultSet);
+ }
+ }
+ else {
+ proxyResultSet = null;
+ }
+ return proxyResultSet;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @SuppressWarnings("unchecked")
+ public final <T> T unwrap(Class<T> iface) throws SQLException
+ {
+ if (iface.isInstance(delegate)) {
+ return (T) delegate;
+ }
+ else if (delegate instanceof Wrapper) {
+ return delegate.unwrap(iface);
+ }
+
+ throw new SQLException("Wrapped statement is not an instance of " + iface);
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/util/ClockSource.java b/src/main/java/com/zaxxer/hikari/util/ClockSource.java
new file mode 100644
index 0000000..6d02450
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/util/ClockSource.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2015 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.util;
+
+import static java.util.concurrent.TimeUnit.DAYS;
+import static java.util.concurrent.TimeUnit.HOURS;
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A resolution-independent provider of current time-stamps and elapsed time
+ * calculations.
+ *
+ * @author Brett Wooldridge
+ */
+public interface ClockSource
+{
+ static ClockSource CLOCK = Factory.create();
+
+ /**
+ * Get the current time-stamp (resolution is opaque).
+ *
+ * @return the current time-stamp
+ */
+ static long currentTime() {
+ return CLOCK.currentTime0();
+ }
+
+ long currentTime0();
+
+ /**
+ * Convert an opaque time-stamp returned by currentTime() into
+ * milliseconds.
+ *
+ * @param time an opaque time-stamp returned by an instance of this class
+ * @return the time-stamp in milliseconds
+ */
+ static long toMillis(long time) {
+ return CLOCK.toMillis0(time);
+ }
+
+ long toMillis0(long time);
+
+ /**
+ * Convert an opaque time-stamp returned by currentTime() into
+ * nanoseconds.
+ *
+ * @param time an opaque time-stamp returned by an instance of this class
+ * @return the time-stamp in nanoseconds
+ */
+ static long toNanos(long time) {
+ return CLOCK.toNanos0(time);
+ }
+
+ long toNanos0(long time);
+
+ /**
+ * Convert an opaque time-stamp returned by currentTime() into an
+ * elapsed time in milliseconds, based on the current instant in time.
+ *
+ * @param startTime an opaque time-stamp returned by an instance of this class
+ * @return the elapsed time between startTime and now in milliseconds
+ */
+ static long elapsedMillis(long startTime) {
+ return CLOCK.elapsedMillis0(startTime);
+ }
+
+ long elapsedMillis0(long startTime);
+
+ /**
+ * Get the difference in milliseconds between two opaque time-stamps returned
+ * by currentTime().
+ *
+ * @param startTime an opaque time-stamp returned by an instance of this class
+ * @param endTime an opaque time-stamp returned by an instance of this class
+ * @return the elapsed time between startTime and endTime in milliseconds
+ */
+ static long elapsedMillis(long startTime, long endTime) {
+ return CLOCK.elapsedMillis0(startTime, endTime);
+ }
+
+ long elapsedMillis0(long startTime, long endTime);
+
+ /**
+ * Convert an opaque time-stamp returned by currentTime() into an
+ * elapsed time in milliseconds, based on the current instant in time.
+ *
+ * @param startTime an opaque time-stamp returned by an instance of this class
+ * @return the elapsed time between startTime and now in milliseconds
+ */
+ static long elapsedNanos(long startTime) {
+ return CLOCK.elapsedNanos0(startTime);
+ }
+
+ long elapsedNanos0(long startTime);
+
+ /**
+ * Get the difference in nanoseconds between two opaque time-stamps returned
+ * by currentTime().
+ *
+ * @param startTime an opaque time-stamp returned by an instance of this class
+ * @param endTime an opaque time-stamp returned by an instance of this class
+ * @return the elapsed time between startTime and endTime in nanoseconds
+ */
+ static long elapsedNanos(long startTime, long endTime) {
+ return CLOCK.elapsedNanos0(startTime, endTime);
+ }
+
+ long elapsedNanos0(long startTime, long endTime);
+
+ /**
+ * Return the specified opaque time-stamp plus the specified number of milliseconds.
+ *
+ * @param time an opaque time-stamp
+ * @param millis milliseconds to add
+ * @return a new opaque time-stamp
+ */
+ static long plusMillis(long time, long millis) {
+ return CLOCK.plusMillis0(time, millis);
+ }
+
+ long plusMillis0(long time, long millis);
+
+ /**
+ * Get the TimeUnit the ClockSource is denominated in.
+ * @return
+ */
+ static TimeUnit getSourceTimeUnit() {
+ return CLOCK.getSourceTimeUnit0();
+ }
+
+ TimeUnit getSourceTimeUnit0();
+
+ /**
+ * Get a String representation of the elapsed time in appropriate magnitude terminology.
+ *
+ * @param startTime an opaque time-stamp
+ * @param endTime an opaque time-stamp
+ * @return a string representation of the elapsed time interval
+ */
+ static String elapsedDisplayString(long startTime, long endTime) {
+ return CLOCK.elapsedDisplayString0(startTime, endTime);
+ }
+
+ default String elapsedDisplayString0(long startTime, long endTime) {
+ long elapsedNanos = elapsedNanos0(startTime, endTime);
+
+ StringBuilder sb = new StringBuilder(elapsedNanos < 0 ? "-" : "");
+ elapsedNanos = Math.abs(elapsedNanos);
+
+ for (TimeUnit unit : TIMEUNITS_DESCENDING) {
+ long converted = unit.convert(elapsedNanos, NANOSECONDS);
+ if (converted > 0) {
+ sb.append(converted).append(TIMEUNIT_DISPLAY_VALUES[unit.ordinal()]);
+ elapsedNanos -= NANOSECONDS.convert(converted, unit);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ TimeUnit[] TIMEUNITS_DESCENDING = {DAYS, HOURS, MINUTES, SECONDS, MILLISECONDS, MICROSECONDS, NANOSECONDS};
+
+ String[] TIMEUNIT_DISPLAY_VALUES = {"ns", "µs", "ms", "s", "m", "h", "d"};
+
+ /**
+ * Factory class used to create a platform-specific ClockSource.
+ */
+ class Factory
+ {
+ private static ClockSource create() {
+ String os = System.getProperty("os.name");
+ if ("Mac OS X".equals(os)) {
+ return new MillisecondClockSource();
+ }
+
+ return new NanosecondClockSource();
+ }
+ }
+
+ final class MillisecondClockSource implements ClockSource
+ {
+ /** {@inheritDoc} */
+ @Override
+ public long currentTime0() {
+ return System.currentTimeMillis();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long elapsedMillis0(final long startTime) {
+ return System.currentTimeMillis() - startTime;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long elapsedMillis0(final long startTime, final long endTime) {
+ return endTime - startTime;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long elapsedNanos0(final long startTime) {
+ return MILLISECONDS.toNanos(System.currentTimeMillis() - startTime);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long elapsedNanos0(final long startTime, final long endTime) {
+ return MILLISECONDS.toNanos(endTime - startTime);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long toMillis0(final long time) {
+ return time;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long toNanos0(final long time) {
+ return MILLISECONDS.toNanos(time);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long plusMillis0(final long time, final long millis) {
+ return time + millis;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public TimeUnit getSourceTimeUnit0() {
+ return MILLISECONDS;
+ }
+ }
+
+ class NanosecondClockSource implements ClockSource
+ {
+ /** {@inheritDoc} */
+ @Override
+ public long currentTime0() {
+ return System.nanoTime();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long toMillis0(final long time) {
+ return NANOSECONDS.toMillis(time);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long toNanos0(final long time) {
+ return time;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long elapsedMillis0(final long startTime) {
+ return NANOSECONDS.toMillis(System.nanoTime() - startTime);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long elapsedMillis0(final long startTime, final long endTime) {
+ return NANOSECONDS.toMillis(endTime - startTime);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long elapsedNanos0(final long startTime) {
+ return System.nanoTime() - startTime;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long elapsedNanos0(final long startTime, final long endTime) {
+ return endTime - startTime;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long plusMillis0(final long time, final long millis) {
+ return time + MILLISECONDS.toNanos(millis);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public TimeUnit getSourceTimeUnit0() {
+ return NANOSECONDS;
+ }
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java b/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java
new file mode 100755
index 0000000..ac0ccb1
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.zaxxer.hikari.util;
+
+import static com.zaxxer.hikari.util.ClockSource.currentTime;
+import static com.zaxxer.hikari.util.ClockSource.elapsedNanos;
+import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_IN_USE;
+import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_NOT_IN_USE;
+import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_REMOVED;
+import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_RESERVED;
+import static java.lang.Thread.yield;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Future;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry;
+
+/**
+ * This is a specialized concurrent bag that achieves superior performance
+ * to LinkedBlockingQueue and LinkedTransferQueue for the purposes of a
+ * connection pool. It uses ThreadLocal storage when possible to avoid
+ * locks, but resorts to scanning a common collection if there are no
+ * available items in the ThreadLocal list. Not-in-use items in the
+ * ThreadLocal lists can be "stolen" when the borrowing thread has none
+ * of its own. It is a "lock-less" implementation using a specialized
+ * AbstractQueuedLongSynchronizer to manage cross-thread signaling.
+ *
+ * Note that items that are "borrowed" from the bag are not actually
+ * removed from any collection, so garbage collection will not occur
+ * even if the reference is abandoned. Thus care must be taken to
+ * "requite" borrowed objects otherwise a memory leak will result. Only
+ * the "remove" method can completely remove an object from the bag.
+ *
+ * @author Brett Wooldridge
+ *
+ * @param <T> the templated type to store in the bag
+ */
+public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseable
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(ConcurrentBag.class);
+
+ private final CopyOnWriteArrayList<T> sharedList;
+ private final boolean weakThreadLocals;
+
+ private final ThreadLocal<List<Object>> threadList;
+ private final IBagStateListener listener;
+ private final AtomicInteger waiters;
+ private volatile boolean closed;
+
+ private final SynchronousQueue<T> handoffQueue;
+
+ public static interface IConcurrentBagEntry
+ {
+ int STATE_NOT_IN_USE = 0;
+ int STATE_IN_USE = 1;
+ int STATE_REMOVED = -1;
+ int STATE_RESERVED = -2;
+
+ boolean compareAndSet(int expectState, int newState);
+ void setState(int newState);
+ int getState();
+ }
+
+ public static interface IBagStateListener
+ {
+ Future<Boolean> addBagItem(int waiting);
+ }
+
+ /**
+ * Construct a ConcurrentBag with the specified listener.
+ *
+ * @param listener the IBagStateListener to attach to this bag
+ */
+ public ConcurrentBag(final IBagStateListener listener)
+ {
+ this.listener = listener;
+ this.weakThreadLocals = useWeakThreadLocals();
+
+ this.handoffQueue = new SynchronousQueue<>(true);
+ this.waiters = new AtomicInteger();
+ this.sharedList = new CopyOnWriteArrayList<>();
+ if (weakThreadLocals) {
+ this.threadList = ThreadLocal.withInitial(() -> new ArrayList<>(16));
+ }
+ else {
+ this.threadList = ThreadLocal.withInitial(() -> new FastList<>(IConcurrentBagEntry.class, 16));
+ }
+ }
+
+ /**
+ * The method will borrow a BagEntry from the bag, blocking for the
+ * specified timeout if none are available.
+ *
+ * @param timeout how long to wait before giving up, in units of unit
+ * @param timeUnit a <code>TimeUnit</code> determining how to interpret the timeout parameter
+ * @return a borrowed instance from the bag or null if a timeout occurs
+ * @throws InterruptedException if interrupted while waiting
+ */
+ public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException
+ {
+ // Try the thread-local list first
+ final List<Object> list = threadList.get();
+ for (int i = list.size() - 1; i >= 0; i--) {
+ final Object entry = list.remove(i);
+ @SuppressWarnings("unchecked")
+ final T bagEntry = weakThreadLocals ? ((WeakReference<T>) entry).get() : (T) entry;
+ if (bagEntry != null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
+ return bagEntry;
+ }
+ }
+
+ // Otherwise, scan the shared list ... then poll the handoff queue
+ final int waiting = waiters.incrementAndGet();
+ try {
+ for (T bagEntry : sharedList) {
+ if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
+ // If we may have stolen another waiter's connection, request another bag add.
+ if (waiting > 1) {
+ listener.addBagItem(waiting - 1);
+ }
+ return bagEntry;
+ }
+ }
+
+ listener.addBagItem(waiting);
+
+ timeout = timeUnit.toNanos(timeout);
+ do {
+ final long start = currentTime();
+ final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS);
+ if (bagEntry == null || bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
+ return bagEntry;
+ }
+
+ timeout -= elapsedNanos(start);
+ } while (timeout > 10_000);
+
+ return null;
+ }
+ finally {
+ waiters.decrementAndGet();
+ }
+ }
+
+ /**
+ * This method will return a borrowed object to the bag. Objects
+ * that are borrowed from the bag but never "requited" will result
+ * in a memory leak.
+ *
+ * @param bagEntry the value to return to the bag
+ * @throws NullPointerException if value is null
+ * @throws IllegalStateException if the bagEntry was not borrowed from the bag
+ */
+ public void requite(final T bagEntry)
+ {
+ bagEntry.setState(STATE_NOT_IN_USE);
+
+ while (waiters.get() > 0) {
+ if (handoffQueue.offer(bagEntry)) {
+ return;
+ }
+ yield();
+ }
+
+ final List<Object> threadLocalList = threadList.get();
+ threadLocalList.add(weakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry);
+ }
+
+ /**
+ * Add a new object to the bag for others to borrow.
+ *
+ * @param bagEntry an object to add to the bag
+ */
+ public void add(final T bagEntry)
+ {
+ if (closed) {
+ LOGGER.info("ConcurrentBag has been closed, ignoring add()");
+ throw new IllegalStateException("ConcurrentBag has been closed, ignoring add()");
+ }
+
+ sharedList.add(bagEntry);
+
+ // spin until a thread takes it or none are waiting
+ while (waiters.get() > 0 && !handoffQueue.offer(bagEntry)) {
+ yield();
+ }
+ }
+
+ /**
+ * Remove a value from the bag. This method should only be called
+ * with objects obtained by <code>borrow(long, TimeUnit)</code> or <code>reserve(T)</code>
+ *
+ * @param bagEntry the value to remove
+ * @return true if the entry was removed, false otherwise
+ * @throws IllegalStateException if an attempt is made to remove an object
+ * from the bag that was not borrowed or reserved first
+ */
+ public boolean remove(final T bagEntry)
+ {
+ if (!bagEntry.compareAndSet(STATE_IN_USE, STATE_REMOVED) && !bagEntry.compareAndSet(STATE_RESERVED, STATE_REMOVED) && !closed) {
+ LOGGER.warn("Attempt to remove an object from the bag that was not borrowed or reserved: {}", bagEntry);
+ return false;
+ }
+
+ final boolean removed = sharedList.remove(bagEntry);
+ if (!removed && !closed) {
+ LOGGER.warn("Attempt to remove an object from the bag that does not exist: {}", bagEntry);
+ }
+
+ return removed;
+ }
+
+ /**
+ * Close the bag to further adds.
+ */
+ @Override
+ public void close()
+ {
+ closed = true;
+ }
+
+ /**
+ * This method provides a "snapshot" in time of the BagEntry
+ * items in the bag in the specified state. It does not "lock"
+ * or reserve items in any way. Call <code>reserve(T)</code>
+ * on items in list before performing any action on them.
+ *
+ * @param state one of the {@link IConcurrentBagEntry} states
+ * @return a possibly empty list of objects having the state specified
+ */
+ public List<T> values(final int state)
+ {
+ return sharedList.stream().filter(e -> e.getState() == state).collect(Collectors.toList());
+ }
+
+ /**
+ * This method provides a "snapshot" in time of the bag items. It
+ * does not "lock" or reserve items in any way. Call <code>reserve(T)</code>
+ * on items in the list, or understand the concurrency implications of
+ * modifying items, before performing any action on them.
+ *
+ * @return a possibly empty list of (all) bag items
+ */
+ @SuppressWarnings("unchecked")
+ public List<T> values()
+ {
+ return (List<T>) sharedList.clone();
+ }
+
+ /**
+ * The method is used to make an item in the bag "unavailable" for
+ * borrowing. It is primarily used when wanting to operate on items
+ * returned by the <code>values(int)</code> method. Items that are
+ * reserved can be removed from the bag via <code>remove(T)</code>
+ * without the need to unreserve them. Items that are not removed
+ * from the bag can be make available for borrowing again by calling
+ * the <code>unreserve(T)</code> method.
+ *
+ * @param bagEntry the item to reserve
+ * @return true if the item was able to be reserved, false otherwise
+ */
+ public boolean reserve(final T bagEntry)
+ {
+ return bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_RESERVED);
+ }
+
+ /**
+ * This method is used to make an item reserved via <code>reserve(T)</code>
+ * available again for borrowing.
+ *
+ * @param bagEntry the item to unreserve
+ */
+ public void unreserve(final T bagEntry)
+ {
+ if (bagEntry.compareAndSet(STATE_RESERVED, STATE_NOT_IN_USE)) {
+ // spin until a thread takes it or none are waiting
+ while (waiters.get() > 0 && !handoffQueue.offer(bagEntry)) {
+ yield();
+ }
+ }
+ else {
+ LOGGER.warn("Attempt to relinquish an object to the bag that was not reserved: {}", bagEntry);
+ }
+ }
+
+ /**
+ * Get the number of threads pending (waiting) for an item from the
+ * bag to become available.
+ *
+ * @return the number of threads waiting for items from the bag
+ */
+ public int getWaitingThreadCount()
+ {
+ return waiters.get();
+ }
+
+ /**
+ * Get a count of the number of items in the specified state at the time of this call.
+ *
+ * @param state the state of the items to count
+ * @return a count of how many items in the bag are in the specified state
+ */
+ public int getCount(final int state)
+ {
+ int count = 0;
+ for (IConcurrentBagEntry e : sharedList) {
+ if (e.getState() == state) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ public int[] getStateCounts()
+ {
+ final int[] states = new int[6];
+ for (IConcurrentBagEntry e : sharedList) {
+ ++states[e.getState()];
+ }
+ states[4] = sharedList.size();
+ states[5] = waiters.get();
+
+ return states;
+ }
+
+ /**
+ * Get the total number of items in the bag.
+ *
+ * @return the number of items in the bag
+ */
+ public int size()
+ {
+ return sharedList.size();
+ }
+
+ public void dumpState()
+ {
+ sharedList.forEach(entry -> LOGGER.info(entry.toString()));
+ }
+
+ /**
+ * Determine whether to use WeakReferences based on whether there is a
+ * custom ClassLoader implementation sitting between this class and the
+ * System ClassLoader.
+ *
+ * @return true if we should use WeakReferences in our ThreadLocals, false otherwise
+ */
+ private boolean useWeakThreadLocals()
+ {
+ try {
+ if (System.getProperty("com.zaxxer.hikari.useWeakReferences") != null) { // undocumented manual override of WeakReference behavior
+ return Boolean.getBoolean("com.zaxxer.hikari.useWeakReferences");
+ }
+
+ return getClass().getClassLoader() != ClassLoader.getSystemClassLoader();
+ }
+ catch (SecurityException se) {
+ return true;
+ }
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/util/DriverDataSource.java b/src/main/java/com/zaxxer/hikari/util/DriverDataSource.java
new file mode 100644
index 0000000..9aa9b6b
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/util/DriverDataSource.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.zaxxer.hikari.util;
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.Enumeration;
+import java.util.Map.Entry;
+import java.util.Properties;
+
+import javax.sql.DataSource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class DriverDataSource implements DataSource
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(DriverDataSource.class);
+
+ private final String jdbcUrl;
+ private final Properties driverProperties;
+ private Driver driver;
+
+ public DriverDataSource(String jdbcUrl, String driverClassName, Properties properties, String username, String password)
+ {
+ this.jdbcUrl = jdbcUrl;
+ this.driverProperties = new Properties();
+
+ for (Entry<Object, Object> entry : properties.entrySet()) {
+ driverProperties.setProperty(entry.getKey().toString(), entry.getValue().toString());
+ }
+
+ if (username != null) {
+ driverProperties.put("user", driverProperties.getProperty("user", username));
+ }
+ if (password != null) {
+ driverProperties.put("password", driverProperties.getProperty("password", password));
+ }
+
+ if (driverClassName != null) {
+ Enumeration<Driver> drivers = DriverManager.getDrivers();
+ while (drivers.hasMoreElements()) {
+ Driver d = drivers.nextElement();
+ if (d.getClass().getName().equals(driverClassName)) {
+ driver = d;
+ break;
+ }
+ }
+
+ if (driver == null) {
+ LOGGER.warn("Registered driver with driverClassName={} was not found, trying direct instantiation.", driverClassName);
+ try {
+ Class<?> driverClass = this.getClass().getClassLoader().loadClass(driverClassName);
+ driver = (Driver) driverClass.newInstance();
+ }
+ catch (Exception e) {
+ LOGGER.warn("Failed to create instance of driver class {}, trying jdbcUrl resolution", driverClassName, e);
+ }
+ }
+ }
+
+ try {
+ if (driver == null) {
+ driver = DriverManager.getDriver(jdbcUrl);
+ }
+ else if (!driver.acceptsURL(jdbcUrl)) {
+ throw new RuntimeException("Driver " + driverClassName + " claims to not accept jdbcUrl, " + jdbcUrl);
+ }
+ }
+ catch (SQLException e) {
+ throw new RuntimeException("Failed to get driver instance for jdbcUrl=" + jdbcUrl, e);
+ }
+ }
+
+ @Override
+ public Connection getConnection() throws SQLException
+ {
+ return driver.connect(jdbcUrl, driverProperties);
+ }
+
+ @Override
+ public Connection getConnection(String username, String password) throws SQLException
+ {
+ return getConnection();
+ }
+
+ @Override
+ public PrintWriter getLogWriter() throws SQLException
+ {
+ throw new SQLFeatureNotSupportedException();
+ }
+
+ @Override
+ public void setLogWriter(PrintWriter logWriter) throws SQLException
+ {
+ throw new SQLFeatureNotSupportedException();
+ }
+
+ @Override
+ public void setLoginTimeout(int seconds) throws SQLException
+ {
+ DriverManager.setLoginTimeout(seconds);
+ }
+
+ @Override
+ public int getLoginTimeout() throws SQLException
+ {
+ return DriverManager.getLoginTimeout();
+ }
+
+ @Override
+ public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException
+ {
+ return driver.getParentLogger();
+ }
+
+ @Override
+ public <T> T unwrap(Class<T> iface) throws SQLException
+ {
+ throw new SQLFeatureNotSupportedException();
+ }
+
+ @Override
+ public boolean isWrapperFor(Class<?> iface) throws SQLException
+ {
+ return false;
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/util/FastList.java b/src/main/java/com/zaxxer/hikari/util/FastList.java
new file mode 100644
index 0000000..a1d42b2
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/util/FastList.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.util;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.NoSuchElementException;
+import java.util.RandomAccess;
+import java.util.Spliterator;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+
+/**
+ * Fast list without range checking.
+ *
+ * @author Brett Wooldridge
+ */
+public final class FastList<T> implements List<T>, RandomAccess, Serializable
+{
+ private static final long serialVersionUID = -4598088075242913858L;
+
+ private final Class<?> clazz;
+ private T[] elementData;
+ private int size;
+
+ /**
+ * Construct a FastList with a default size of 32.
+ * @param clazz the Class stored in the collection
+ */
+ @SuppressWarnings("unchecked")
+ public FastList(Class<?> clazz)
+ {
+ this.elementData = (T[]) Array.newInstance(clazz, 32);
+ this.clazz = clazz;
+ }
+
+ /**
+ * Construct a FastList with a specified size.
+ * @param clazz the Class stored in the collection
+ * @param capacity the initial size of the FastList
+ */
+ @SuppressWarnings("unchecked")
+ public FastList(Class<?> clazz, int capacity)
+ {
+ this.elementData = (T[]) Array.newInstance(clazz, capacity);
+ this.clazz = clazz;
+ }
+
+ /**
+ * Add an element to the tail of the FastList.
+ *
+ * @param element the element to add
+ */
+ @Override
+ public boolean add(T element)
+ {
+ if (size < elementData.length) {
+ elementData[size++] = element;
+ }
+ else {
+ // overflow-conscious code
+ final int oldCapacity = elementData.length;
+ final int newCapacity = oldCapacity << 1;
+ @SuppressWarnings("unchecked")
+ final T[] newElementData = (T[]) Array.newInstance(clazz, newCapacity);
+ System.arraycopy(elementData, 0, newElementData, 0, oldCapacity);
+ newElementData[size++] = element;
+ elementData = newElementData;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get the element at the specified index.
+ *
+ * @param index the index of the element to get
+ * @return the element, or ArrayIndexOutOfBounds is thrown if the index is invalid
+ */
+ @Override
+ public T get(int index)
+ {
+ return elementData[index];
+ }
+
+ /**
+ * Remove the last element from the list. No bound check is performed, so if this
+ * method is called on an empty list and ArrayIndexOutOfBounds exception will be
+ * thrown.
+ *
+ * @return the last element of the list
+ */
+ public T removeLast()
+ {
+ T element = elementData[--size];
+ elementData[size] = null;
+ return element;
+ }
+
+ /**
+ * This remove method is most efficient when the element being removed
+ * is the last element. Equality is identity based, not equals() based.
+ * Only the first matching element is removed.
+ *
+ * @param element the element to remove
+ */
+ @Override
+ public boolean remove(Object element)
+ {
+ for (int index = size - 1; index >= 0; index--) {
+ if (element == elementData[index]) {
+ final int numMoved = size - index - 1;
+ if (numMoved > 0) {
+ System.arraycopy(elementData, index + 1, elementData, index, numMoved);
+ }
+ elementData[--size] = null;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Clear the FastList.
+ */
+ @Override
+ public void clear()
+ {
+ for (int i = 0; i < size; i++) {
+ elementData[i] = null;
+ }
+
+ size = 0;
+ }
+
+ /**
+ * Get the current number of elements in the FastList.
+ *
+ * @return the number of current elements
+ */
+ @Override
+ public int size()
+ {
+ return size;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isEmpty()
+ {
+ return size == 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public T set(int index, T element)
+ {
+ T old = elementData[index];
+ elementData[index] = element;
+ return old;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public T remove(int index)
+ {
+ if (size == 0) {
+ return null;
+ }
+
+ final T old = elementData[index];
+
+ final int numMoved = size - index - 1;
+ if (numMoved > 0) {
+ System.arraycopy(elementData, index + 1, elementData, index, numMoved);
+ }
+
+ elementData[--size] = null;
+
+ return old;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean contains(Object o)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Iterator<T> iterator()
+ {
+ return new Iterator<T>() {
+ private int index;
+
+ @Override
+ public boolean hasNext()
+ {
+ return index < size;
+ }
+
+ @Override
+ public T next()
+ {
+ if (index < size) {
+ return elementData[index++];
+ }
+
+ throw new NoSuchElementException("No more elements in FastList");
+ }
+ };
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object[] toArray()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public <E> E[] toArray(E[] a)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean containsAll(Collection<?> c)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean addAll(Collection<? extends T> c)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean addAll(int index, Collection<? extends T> c)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean removeAll(Collection<?> c)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean retainAll(Collection<?> c)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void add(int index, T element)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int indexOf(Object o)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int lastIndexOf(Object o)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ListIterator<T> listIterator()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ListIterator<T> listIterator(int index)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public List<T> subList(int fromIndex, int toIndex)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object clone()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void forEach(Consumer<? super T> action)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Spliterator<T> spliterator()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean removeIf(Predicate<? super T> filter)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void replaceAll(UnaryOperator<T> operator)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void sort(Comparator<? super T> c)
+ {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/util/JavassistProxyFactory.java b/src/main/java/com/zaxxer/hikari/util/JavassistProxyFactory.java
new file mode 100644
index 0000000..c1b2cb0
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/util/JavassistProxyFactory.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.util;
+
+import java.lang.reflect.Array;
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.zaxxer.hikari.pool.ProxyCallableStatement;
+import com.zaxxer.hikari.pool.ProxyConnection;
+import com.zaxxer.hikari.pool.ProxyFactory;
+import com.zaxxer.hikari.pool.ProxyPreparedStatement;
+import com.zaxxer.hikari.pool.ProxyResultSet;
+import com.zaxxer.hikari.pool.ProxyStatement;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtMethod;
+import javassist.CtNewMethod;
+import javassist.LoaderClassPath;
+import javassist.Modifier;
+import javassist.NotFoundException;
+import javassist.bytecode.ClassFile;
+
+/**
+ * This class generates the proxy objects for {@link Connection}, {@link Statement},
+ * {@link PreparedStatement}, and {@link CallableStatement}. Additionally it injects
+ * method bodies into the {@link ProxyFactory} class methods that can instantiate
+ * instances of the generated proxies.
+ *
+ * @author Brett Wooldridge
+ */
+public final class JavassistProxyFactory
+{
+ private static ClassPool classPool;
+
+ public static void main(String... args)
+ {
+ classPool = new ClassPool();
+ classPool.importPackage("java.sql");
+ classPool.appendClassPath(new LoaderClassPath(JavassistProxyFactory.class.getClassLoader()));
+
+ try {
+ // Cast is not needed for these
+ String methodBody = "{ try { return delegate.method($$); } catch (SQLException e) { throw checkException(e); } }";
+ generateProxyClass(Connection.class, ProxyConnection.class.getName(), methodBody);
+ generateProxyClass(Statement.class, ProxyStatement.class.getName(), methodBody);
+ generateProxyClass(ResultSet.class, ProxyResultSet.class.getName(), methodBody);
+
+ // For these we have to cast the delegate
+ methodBody = "{ try { return ((cast) delegate).method($$); } catch (SQLException e) { throw checkException(e); } }";
+ generateProxyClass(PreparedStatement.class, ProxyPreparedStatement.class.getName(), methodBody);
+ generateProxyClass(CallableStatement.class, ProxyCallableStatement.class.getName(), methodBody);
+
+ modifyProxyFactory();
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static void modifyProxyFactory() throws Exception
+ {
+ System.out.println("Generating method bodies for com.zaxxer.hikari.proxy.ProxyFactory");
+
+ String packageName = ProxyConnection.class.getPackage().getName();
+ CtClass proxyCt = classPool.getCtClass("com.zaxxer.hikari.pool.ProxyFactory");
+ for (CtMethod method : proxyCt.getMethods()) {
+ switch (method.getName()) {
+ case "getProxyConnection":
+ method.setBody("{return new " + packageName + ".HikariProxyConnection($$);}");
+ break;
+ case "getProxyStatement":
+ method.setBody("{return new " + packageName + ".HikariProxyStatement($$);}");
+ break;
+ case "getProxyPreparedStatement":
+ method.setBody("{return new " + packageName + ".HikariProxyPreparedStatement($$);}");
+ break;
+ case "getProxyCallableStatement":
+ method.setBody("{return new " + packageName + ".HikariProxyCallableStatement($$);}");
+ break;
+ case "getProxyResultSet":
+ method.setBody("{return new " + packageName + ".HikariProxyResultSet($$);}");
+ break;
+ default:
+ // unhandled method
+ break;
+ }
+ }
+
+ proxyCt.writeFile("target/classes");
+ }
+
+ /**
+ * Generate Javassist Proxy Classes
+ */
+ private static <T> void generateProxyClass(Class<T> primaryInterface, String superClassName, String methodBody) throws Exception
+ {
+ String newClassName = superClassName.replaceAll("(.+)\\.(\\w+)", "$1.Hikari$2");
+
+ CtClass superCt = classPool.getCtClass(superClassName);
+ CtClass targetCt = classPool.makeClass(newClassName, superCt);
+ targetCt.setModifiers(Modifier.FINAL);
+
+ System.out.println("Generating " + newClassName);
+
+ targetCt.setModifiers(Modifier.PUBLIC);
+
+ // Make a set of method signatures we inherit implementation for, so we don't generate delegates for these
+ Set<String> superSigs = new HashSet<>();
+ for (CtMethod method : superCt.getMethods()) {
+ if ((method.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
+ superSigs.add(method.getName() + method.getSignature());
+ }
+ }
+
+ Set<String> methods = new HashSet<>();
+ Set<Class<?>> interfaces = getAllInterfaces(primaryInterface);
+ for (Class<?> intf : interfaces) {
+ CtClass intfCt = classPool.getCtClass(intf.getName());
+ targetCt.addInterface(intfCt);
+ for (CtMethod intfMethod : intfCt.getDeclaredMethods()) {
+ final String signature = intfMethod.getName() + intfMethod.getSignature();
+
+ // don't generate delegates for methods we override
+ if (superSigs.contains(signature)) {
+ continue;
+ }
+
+ // Ignore already added methods that come from other interfaces
+ if (methods.contains(signature)) {
+ continue;
+ }
+
+ // Track what methods we've added
+ methods.add(signature);
+
+ // Clone the method we want to inject into
+ CtMethod method = CtNewMethod.copy(intfMethod, targetCt, null);
+
+ String modifiedBody = methodBody;
+
+ // If the super-Proxy has concrete methods (non-abstract), transform the call into a simple super.method() call
+ CtMethod superMethod = superCt.getMethod(intfMethod.getName(), intfMethod.getSignature());
+ if ((superMethod.getModifiers() & Modifier.ABSTRACT) != Modifier.ABSTRACT && !isDefaultMethod(intf, intfCt, intfMethod)) {
+ modifiedBody = modifiedBody.replace("((cast) ", "");
+ modifiedBody = modifiedBody.replace("delegate", "super");
+ modifiedBody = modifiedBody.replace("super)", "super");
+ }
+
+ modifiedBody = modifiedBody.replace("cast", primaryInterface.getName());
+
+ // Generate a method that simply invokes the same method on the delegate
+ if (isThrowsSqlException(intfMethod)) {
+ modifiedBody = modifiedBody.replace("method", method.getName());
+ }
+ else {
+ modifiedBody = "{ return ((cast) delegate).method($$); }".replace("method", method.getName()).replace("cast", primaryInterface.getName());
+ }
+
+ if (method.getReturnType() == CtClass.voidType) {
+ modifiedBody = modifiedBody.replace("return", "");
+ }
+
+ method.setBody(modifiedBody);
+ targetCt.addMethod(method);
+ }
+ }
+
+ targetCt.getClassFile().setMajorVersion(ClassFile.JAVA_7);
+ targetCt.writeFile("target/classes");
+ }
+
+ private static boolean isThrowsSqlException(CtMethod method)
+ {
+ try {
+ for (CtClass clazz : method.getExceptionTypes()) {
+ if (clazz.getSimpleName().equals("SQLException")) {
+ return true;
+ }
+ }
+ }
+ catch (NotFoundException e) {
+ // fall thru
+ }
+
+ return false;
+ }
+
+ private static boolean isDefaultMethod(Class<?> intf, CtClass intfCt, CtMethod intfMethod) throws Exception
+ {
+ List<Class<?>> paramTypes = new ArrayList<>();
+
+ for (CtClass pt : intfMethod.getParameterTypes()) {
+ paramTypes.add(toJavaClass(pt));
+ }
+
+ return intf.getDeclaredMethod(intfMethod.getName(), paramTypes.toArray(new Class[paramTypes.size()])).toString().contains("default ");
+ }
+
+ private static Set<Class<?>> getAllInterfaces(Class<?> clazz)
+ {
+ Set<Class<?>> interfaces = new HashSet<>();
+ for (Class<?> intf : Arrays.asList(clazz.getInterfaces())) {
+ if (intf.getInterfaces().length > 0) {
+ interfaces.addAll(getAllInterfaces(intf));
+ }
+ interfaces.add(intf);
+ }
+ if (clazz.getSuperclass() != null) {
+ interfaces.addAll(getAllInterfaces(clazz.getSuperclass()));
+ }
+
+ if (clazz.isInterface()) {
+ interfaces.add(clazz);
+ }
+
+ return interfaces;
+ }
+
+ private static Class<?> toJavaClass(CtClass cls) throws Exception
+ {
+ if (cls.getName().endsWith("[]")) {
+ return Array.newInstance(toJavaClass(cls.getName().replace("[]", "")), 0).getClass();
+ }
+ else {
+ return toJavaClass(cls.getName());
+ }
+ }
+
+ private static Class<?> toJavaClass(String cn) throws Exception
+ {
+ switch (cn) {
+ case "int":
+ return int.class;
+ case "long":
+ return long.class;
+ case "short":
+ return short.class;
+ case "byte":
+ return byte.class;
+ case "float":
+ return float.class;
+ case "double":
+ return double.class;
+ case "boolean":
+ return boolean.class;
+ case "char":
+ return char.class;
+ case "void":
+ return void.class;
+ default:
+ return Class.forName(cn);
+ }
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/util/PropertyElf.java b/src/main/java/com/zaxxer/hikari/util/PropertyElf.java
new file mode 100644
index 0000000..f03b8c6
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/util/PropertyElf.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.util;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.zaxxer.hikari.HikariConfig;
+
+/**
+ * A class that reflectively sets bean properties on a target object.
+ *
+ * @author Brett Wooldridge
+ */
+public final class PropertyElf
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(PropertyElf.class);
+
+ private static final Pattern GETTER_PATTERN = Pattern.compile("(get|is)[A-Z].+");
+
+ public static void setTargetFromProperties(final Object target, final Properties properties)
+ {
+ if (target == null || properties == null) {
+ return;
+ }
+
+ List<Method> methods = Arrays.asList(target.getClass().getMethods());
+ properties.forEach((key, value) -> {
+ if (target instanceof HikariConfig && key.toString().startsWith("dataSource.")) {
+ ((HikariConfig) target).addDataSourceProperty(key.toString().substring("dataSource.".length()), value);
+ }
+ else {
+ setProperty(target, key.toString(), value, methods);
+ }
+ });
+ }
+
+ /**
+ * Get the bean-style property names for the specified object.
+ *
+ * @param targetClass the target object
+ * @return a set of property names
+ */
+ public static Set<String> getPropertyNames(final Class<?> targetClass)
+ {
+ HashSet<String> set = new HashSet<>();
+ Matcher matcher = GETTER_PATTERN.matcher("");
+ for (Method method : targetClass.getMethods()) {
+ String name = method.getName();
+ if (method.getParameterTypes().length == 0 && matcher.reset(name).matches()) {
+ name = name.replaceFirst("(get|is)", "");
+ try {
+ if (targetClass.getMethod("set" + name, method.getReturnType()) != null) {
+ name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
+ set.add(name);
+ }
+ }
+ catch (Exception e) {
+ continue;
+ }
+ }
+ }
+
+ return set;
+ }
+
+ public static Object getProperty(final String propName, final Object target)
+ {
+ try {
+ // use the english locale to avoid the infamous turkish locale bug
+ String capitalized = "get" + propName.substring(0, 1).toUpperCase(Locale.ENGLISH) + propName.substring(1);
+ Method method = target.getClass().getMethod(capitalized);
+ return method.invoke(target);
+ }
+ catch (Exception e) {
+ try {
+ String capitalized = "is" + propName.substring(0, 1).toUpperCase(Locale.ENGLISH) + propName.substring(1);
+ Method method = target.getClass().getMethod(capitalized);
+ return method.invoke(target);
+ }
+ catch (Exception e2) {
+ return null;
+ }
+ }
+ }
+
+ public static Properties copyProperties(final Properties props)
+ {
+ Properties copy = new Properties();
+ props.forEach((key, value) -> copy.setProperty(key.toString(), value.toString()));
+ return copy;
+ }
+
+ private static void setProperty(final Object target, final String propName, final Object propValue, final List<Method> methods)
+ {
+ // use the english locale to avoid the infamous turkish locale bug
+ String methodName = "set" + propName.substring(0, 1).toUpperCase(Locale.ENGLISH) + propName.substring(1);
+ Method writeMethod = methods.stream().filter(m -> m.getName().equals(methodName) && m.getParameterCount() == 1).findFirst().orElse(null);
+
+ if (writeMethod == null) {
+ String methodName2 = "set" + propName.toUpperCase(Locale.ENGLISH);
+ writeMethod = methods.stream().filter(m -> m.getName().equals(methodName2) && m.getParameterCount() == 1).findFirst().orElse(null);
+ }
+
+ if (writeMethod == null) {
+ LOGGER.error("Property {} does not exist on target {}", propName, target.getClass());
+ throw new RuntimeException(String.format("Property %s does not exist on target %s", propName, target.getClass()));
+ }
+
+ try {
+ Class<?> paramClass = writeMethod.getParameterTypes()[0];
+ if (paramClass == int.class) {
+ writeMethod.invoke(target, Integer.parseInt(propValue.toString()));
+ }
+ else if (paramClass == long.class) {
+ writeMethod.invoke(target, Long.parseLong(propValue.toString()));
+ }
+ else if (paramClass == boolean.class || paramClass == Boolean.class) {
+ writeMethod.invoke(target, Boolean.parseBoolean(propValue.toString()));
+ }
+ else if (paramClass == String.class) {
+ writeMethod.invoke(target, propValue.toString());
+ }
+ else {
+ writeMethod.invoke(target, propValue);
+ }
+ }
+ catch (Exception e) {
+ LOGGER.error("Failed to set property {} on target {}", propName, target.getClass(), e);
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/util/QueuedSequenceSynchronizer.java b/src/main/java/com/zaxxer/hikari/util/QueuedSequenceSynchronizer.java
new file mode 100644
index 0000000..eac7ec7
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/util/QueuedSequenceSynchronizer.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2015 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.util;
+
+import java.util.concurrent.atomic.LongAdder;
+import java.util.concurrent.locks.AbstractQueuedLongSynchronizer;
+
+/**
+ * A specialized wait/notify class useful for resource tracking through the
+ * use of a monotonically-increasing long sequence.
+ * <p>
+ * When a shared resource becomes available the {@link #signal()} method should
+ * be called unconditionally.
+ * <p>
+ * A thread wishing to acquire a shared resource should: <br>
+ * <ul>
+ * <li>Obtain the current sequence from the {@link #currentSequence()} method </li>
+ * <li>Call {@link #waitUntilSequenceExceeded(long, long)} with that sequence. </li>
+ * <li>Upon receiving a <code>true</code> result from {@link #waitUntilSequenceExceeded(long, long)},
+ * the current sequence should again be obtained from the {@link #currentSequence()} method,
+ * and an attempt to acquire the resource should be made. </li>
+ * <li>If the shared resource cannot be acquired, the thread should again call
+ * {@link #waitUntilSequenceExceeded(long, long)} with the previously obtained sequence. </li>
+ * <li>If <code>false</code> is received from {@link #waitUntilSequenceExceeded(long, long)}
+ * then a timeout has occurred. </li>
+ * </ul>
+ * <p>
+ * When running on Java 8 and above, this class leverages the fact that when {@link LongAdder}
+ * is monotonically increasing, and only {@link LongAdder#increment()} and {@link LongAdder#sum()}
+ * are used, it can be relied on to be Sequentially Consistent.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Sequential_consistency">Java Spec</a>
+ * @author Brett Wooldridge
+ */
+public final class QueuedSequenceSynchronizer
+{
+ private final Sequence sequence;
+ private final Synchronizer synchronizer;
+
+ /**
+ * Default constructor
+ */
+ public QueuedSequenceSynchronizer()
+ {
+ this.synchronizer = new Synchronizer();
+ this.sequence = Sequence.Factory.create();
+ }
+
+ /**
+ * Signal any waiting threads.
+ */
+ public void signal()
+ {
+ synchronizer.releaseShared(1);
+ }
+
+ /**
+ * Get the current sequence.
+ *
+ * @return the current sequence
+ */
+ public long currentSequence()
+ {
+ return sequence.get();
+ }
+
+ /**
+ * Block the current thread until the current sequence exceeds the specified threshold, or
+ * until the specified timeout is reached.
+ *
+ * @param sequence the threshold the sequence must reach before this thread becomes unblocked
+ * @param nanosTimeout a nanosecond timeout specifying the maximum time to wait
+ * @return true if the threshold was reached, false if the wait timed out
+ * @throws InterruptedException if the thread is interrupted while waiting
+ */
+ public boolean waitUntilSequenceExceeded(long sequence, long nanosTimeout) throws InterruptedException
+ {
+ return synchronizer.tryAcquireSharedNanos(sequence, nanosTimeout);
+ }
+
+ /**
+ * Queries whether any threads are waiting to for the sequence to reach a particular threshold.
+ *
+ * @return true if there may be other threads waiting for a sequence threshold to be reached
+ */
+ public boolean hasQueuedThreads()
+ {
+ return synchronizer.hasQueuedThreads();
+ }
+
+ /**
+ * Returns an estimate of the number of threads waiting for a sequence threshold to be reached. The
+ * value is only an estimate because the number of threads may change dynamically while this method
+ * traverses internal data structures. This method is designed for use in monitoring system state,
+ * not for synchronization control.
+ *
+ * @return the estimated number of threads waiting for a sequence threshold to be reached
+ */
+ public int getQueueLength()
+ {
+ return synchronizer.getQueueLength();
+ }
+
+ private final class Synchronizer extends AbstractQueuedLongSynchronizer
+ {
+ private static final long serialVersionUID = 104753538004341218L;
+
+ /** {@inheritDoc} */
+ @Override
+ protected long tryAcquireShared(final long seq)
+ {
+ return sequence.get() - (seq + 1);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean tryReleaseShared(final long unused)
+ {
+ sequence.increment();
+ return true;
+ }
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/util/Sequence.java b/src/main/java/com/zaxxer/hikari/util/Sequence.java
new file mode 100644
index 0000000..b7abd3c
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/util/Sequence.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2015 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.util;
+
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.LongAdder;
+
+/**
+ * A monotonically increasing long sequence.
+ *
+ * @author brettw
+ */
+@SuppressWarnings("serial")
+public interface Sequence
+{
+ /**
+ * Increments the current sequence by one.
+ */
+ void increment();
+
+ /**
+ * Get the current sequence.
+ *
+ * @return the current sequence.
+ */
+ long get();
+
+ /**
+ * Factory class used to create a platform-specific ClockSource.
+ */
+ final class Factory
+ {
+ public static Sequence create()
+ {
+ if (!Boolean.getBoolean("com.zaxxer.hikari.useAtomicLongSequence")) {
+ return new Java8Sequence();
+ }
+ else {
+ return new Java7Sequence();
+ }
+ }
+ }
+
+ final class Java7Sequence extends AtomicLong implements Sequence {
+ @Override
+ public void increment() {
+ this.incrementAndGet();
+ }
+ }
+
+ final class Java8Sequence extends LongAdder implements Sequence {
+ @Override
+ public long get() {
+ return this.sum();
+ }
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/util/SuspendResumeLock.java b/src/main/java/com/zaxxer/hikari/util/SuspendResumeLock.java
new file mode 100644
index 0000000..0be8113
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/util/SuspendResumeLock.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.util;
+
+import java.util.concurrent.Semaphore;
+
+/**
+ * This class implements a lock that can be used to suspend and resume the pool. It
+ * also provides a faux implementation that is used when the feature is disabled that
+ * hopefully gets fully "optimized away" by the JIT.
+ *
+ * @author Brett Wooldridge
+ */
+public class SuspendResumeLock
+{
+ public static final SuspendResumeLock FAUX_LOCK = new SuspendResumeLock(false) {
+ @Override
+ public void acquire() {}
+
+ @Override
+ public void release() {}
+
+ @Override
+ public void suspend() {}
+
+ @Override
+ public void resume() {}
+ };
+
+ private static final int MAX_PERMITS = 10000;
+ private final Semaphore acquisitionSemaphore;
+
+ /**
+ * Default constructor
+ */
+ public SuspendResumeLock()
+ {
+ this(true);
+ }
+
+ private SuspendResumeLock(final boolean createSemaphore)
+ {
+ acquisitionSemaphore = (createSemaphore ? new Semaphore(MAX_PERMITS, true) : null);
+ }
+
+ public void acquire()
+ {
+ acquisitionSemaphore.acquireUninterruptibly();
+ }
+
+ public void release()
+ {
+ acquisitionSemaphore.release();
+ }
+
+ public void suspend()
+ {
+ acquisitionSemaphore.acquireUninterruptibly(MAX_PERMITS);
+ }
+
+ public void resume()
+ {
+ acquisitionSemaphore.release(MAX_PERMITS);
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/util/UtilityElf.java b/src/main/java/com/zaxxer/hikari/util/UtilityElf.java
new file mode 100644
index 0000000..35b04d1
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/util/UtilityElf.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.util;
+
+import static java.lang.Thread.currentThread;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.sql.Connection;
+import java.util.Locale;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ *
+ * @author Brett Wooldridge
+ */
+public final class UtilityElf
+{
+ /**
+ *
+ * @return null if string is null or empty
+ */
+ public static String getNullIfEmpty(final String text)
+ {
+ return text == null ? null : text.trim().isEmpty() ? null : text.trim();
+ }
+
+ /**
+ * Sleep and suppress InterruptedException (but re-signal it).
+ *
+ * @param millis the number of milliseconds to sleep
+ */
+ public static void quietlySleep(final long millis)
+ {
+ try {
+ Thread.sleep(millis);
+ }
+ catch (InterruptedException e) {
+ // I said be quiet!
+ currentThread().interrupt();
+ }
+ }
+
+ /**
+ * Create and instance of the specified class using the constructor matching the specified
+ * arguments.
+ *
+ * @param <T> the class type
+ * @param className the name of the class to instantiate
+ * @param clazz a class to cast the result as
+ * @param args arguments to a constructor
+ * @return an instance of the specified class
+ */
+ public static <T> T createInstance(final String className, final Class<T> clazz, final Object... args)
+ {
+ if (className == null) {
+ return null;
+ }
+
+ try {
+ Class<?> loaded = UtilityElf.class.getClassLoader().loadClass(className);
+ if (args.length == 0) {
+ return clazz.cast(loaded.newInstance());
+ }
+
+ Class<?>[] argClasses = new Class<?>[args.length];
+ for (int i = 0; i < args.length; i++) {
+ argClasses[i] = args[i].getClass();
+ }
+ Constructor<?> constructor = loaded.getConstructor(argClasses);
+ return clazz.cast(constructor.newInstance(args));
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Create a ThreadPoolExecutor.
+ *
+ * @param queueSize the queue size
+ * @param threadName the thread name
+ * @param threadFactory an optional ThreadFactory
+ * @param policy the RejectedExecutionHandler policy
+ * @return a ThreadPoolExecutor
+ */
+ public static ThreadPoolExecutor createThreadPoolExecutor(final int queueSize, final String threadName, ThreadFactory threadFactory, final RejectedExecutionHandler policy)
+ {
+ if (threadFactory == null) {
+ threadFactory = new DefaultThreadFactory(threadName, true);
+ }
+
+ LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(queueSize);
+ ThreadPoolExecutor executor = new ThreadPoolExecutor(1 /*core*/, 1 /*max*/, 5 /*keepalive*/, SECONDS, queue, threadFactory, policy);
+ executor.allowCoreThreadTimeOut(true);
+ return executor;
+ }
+
+ /**
+ * Create a ThreadPoolExecutor.
+ *
+ * @param queue the BlockingQueue to use
+ * @param threadName the thread name
+ * @param threadFactory an optional ThreadFactory
+ * @param policy the RejectedExecutionHandler policy
+ * @return a ThreadPoolExecutor
+ */
+ public static ThreadPoolExecutor createThreadPoolExecutor(final BlockingQueue<Runnable> queue, final String threadName, ThreadFactory threadFactory, final RejectedExecutionHandler policy)
+ {
+ if (threadFactory == null) {
+ threadFactory = new DefaultThreadFactory(threadName, true);
+ }
+
+ ThreadPoolExecutor executor = new ThreadPoolExecutor(1 /*core*/, 1 /*max*/, 5 /*keepalive*/, SECONDS, queue, threadFactory, policy);
+ executor.allowCoreThreadTimeOut(true);
+ return executor;
+ }
+
+ // ***********************************************************************
+ // Misc. public methods
+ // ***********************************************************************
+
+ /**
+ * Get the int value of a transaction isolation level by name.
+ *
+ * @param transactionIsolationName the name of the transaction isolation level
+ * @return the int value of the isolation level or -1
+ */
+ public static int getTransactionIsolation(final String transactionIsolationName)
+ {
+ if (transactionIsolationName != null) {
+ try {
+ // use the english locale to avoid the infamous turkish locale bug
+ final String upperName = transactionIsolationName.toUpperCase(Locale.ENGLISH);
+ if (upperName.startsWith("TRANSACTION_")) {
+ Field field = Connection.class.getField(upperName);
+ return field.getInt(null);
+ }
+ final int level = Integer.parseInt(transactionIsolationName);
+ switch (level) {
+ case Connection.TRANSACTION_READ_UNCOMMITTED:
+ case Connection.TRANSACTION_READ_COMMITTED:
+ case Connection.TRANSACTION_REPEATABLE_READ:
+ case Connection.TRANSACTION_SERIALIZABLE:
+ case Connection.TRANSACTION_NONE:
+ return level;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+ catch (Exception e) {
+ throw new IllegalArgumentException("Invalid transaction isolation value: " + transactionIsolationName);
+ }
+ }
+
+ return -1;
+ }
+
+ public static final class DefaultThreadFactory implements ThreadFactory {
+
+ private final String threadName;
+ private final boolean daemon;
+
+ public DefaultThreadFactory(String threadName, boolean daemon) {
+ this.threadName = threadName;
+ this.daemon = daemon;
+ }
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread thread = new Thread(r, threadName);
+ thread.setDaemon(daemon);
+ return thread;
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/db/BasicPoolTest.java b/src/test/java/com/zaxxer/hikari/db/BasicPoolTest.java
new file mode 100644
index 0000000..c9040b0
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/db/BasicPoolTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2016 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.db;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.getPool;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.pool.HikariPool;
+
+/**
+ * @author brettw
+ *
+ */
+public class BasicPoolTest
+{
+ @Before
+ public void setup() throws SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(1);
+ config.setMaximumPoolSize(2);
+ config.setConnectionTestQuery("SELECT 1");
+ config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource");
+ config.addDataSourceProperty("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
+
+ try (HikariDataSource ds = new HikariDataSource(config);
+ Connection conn = ds.getConnection();
+ Statement stmt = conn.createStatement()) {
+ stmt.executeUpdate("DROP TABLE IF EXISTS basic_pool_test");
+ stmt.executeUpdate("CREATE TABLE basic_pool_test ("
+ + "id INTEGER NOT NULL IDENTITY PRIMARY KEY, "
+ + "timestamp TIMESTAMP, "
+ + "string VARCHAR(128), "
+ + "string_from_number NUMERIC "
+ + ")");
+ }
+ }
+
+ @Test
+ public void testIdleTimeout() throws InterruptedException, SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(5);
+ config.setMaximumPoolSize(10);
+ config.setConnectionTestQuery("SELECT 1");
+ config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource");
+ config.addDataSourceProperty("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
+
+ System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "1000");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs");
+
+ SECONDS.sleep(1);
+
+ HikariPool pool = getPool(ds);
+
+ ds.setIdleTimeout(3000);
+
+ assertEquals("Total connections not as expected", 5, pool.getTotalConnections());
+ assertEquals("Idle connections not as expected", 5, pool.getIdleConnections());
+
+ try (Connection connection = ds.getConnection()) {
+ Assert.assertNotNull(connection);
+
+ MILLISECONDS.sleep(1500);
+
+ assertEquals("Second total connections not as expected", 6, pool.getTotalConnections());
+ assertEquals("Second idle connections not as expected", 5, pool.getIdleConnections());
+ }
+
+ assertEquals("Idle connections not as expected", 6, pool.getIdleConnections());
+
+ SECONDS.sleep(2);
+
+ assertEquals("Third total connections not as expected", 5, pool.getTotalConnections());
+ assertEquals("Third idle connections not as expected", 5, pool.getIdleConnections());
+ }
+ }
+
+ @Test
+ public void testIdleTimeout2() throws InterruptedException, SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMaximumPoolSize(50);
+ config.setConnectionTestQuery("SELECT 1");
+ config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource");
+ config.addDataSourceProperty("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
+
+ System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "1000");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs");
+
+ SECONDS.sleep(1);
+
+ HikariPool pool = getPool(ds);
+
+ ds.setIdleTimeout(3000);
+
+ assertEquals("Total connections not as expected", 50, pool.getTotalConnections());
+ assertEquals("Idle connections not as expected", 50, pool.getIdleConnections());
+
+ try (Connection connection = ds.getConnection()) {
+ assertNotNull(connection);
+
+ MILLISECONDS.sleep(1500);
+
+ assertEquals("Second total connections not as expected", 50, pool.getTotalConnections());
+ assertEquals("Second idle connections not as expected", 49, pool.getIdleConnections());
+ }
+
+ assertEquals("Idle connections not as expected", 50, pool.getIdleConnections());
+
+ SECONDS.sleep(3);
+
+ assertEquals("Third total connections not as expected", 50, pool.getTotalConnections());
+ assertEquals("Third idle connections not as expected", 50, pool.getIdleConnections());
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/metrics/dropwizard/CodaHaleMetricsTrackerTest.java b/src/test/java/com/zaxxer/hikari/metrics/dropwizard/CodaHaleMetricsTrackerTest.java
new file mode 100755
index 0000000..8e2164c
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/metrics/dropwizard/CodaHaleMetricsTrackerTest.java
@@ -0,0 +1,39 @@
+package com.zaxxer.hikari.metrics.dropwizard;
+
+import static org.mockito.Mockito.verify;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import com.codahale.metrics.MetricRegistry;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CodaHaleMetricsTrackerTest {
+
+ @Mock
+ public MetricRegistry mockMetricRegistry;
+
+ private CodaHaleMetricsTracker testee;
+
+ @Before
+ public void setup(){
+ testee = new CodaHaleMetricsTracker("mypool", null, mockMetricRegistry);
+ }
+
+ @Test
+ public void close() throws Exception {
+ testee.close();
+
+ verify(mockMetricRegistry).remove("mypool.pool.Wait");
+ verify(mockMetricRegistry).remove("mypool.pool.Usage");
+ verify(mockMetricRegistry).remove("mypool.pool.ConnectionCreation");
+ verify(mockMetricRegistry).remove("mypool.pool.ConnectionTimeoutRate");
+ verify(mockMetricRegistry).remove("mypool.pool.TotalConnections");
+ verify(mockMetricRegistry).remove("mypool.pool.IdleConnections");
+ verify(mockMetricRegistry).remove("mypool.pool.ActiveConnections");
+ verify(mockMetricRegistry).remove("mypool.pool.PendingConnections");
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java b/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java
new file mode 100644
index 0000000..46b2e74
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.metrics.prometheus;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.sql.Connection;
+
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.mocks.StubConnection;
+
+import io.prometheus.client.CollectorRegistry;
+
+public class HikariCPCollectorTest {
+ @Test
+ public void noConnection() throws Exception {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ StubConnection.slowCreate = true;
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ assertThat(getValue("hikaricp_active_connections", "noConnection"), is(0.0));
+ assertThat(getValue("hikaricp_idle_connections", "noConnection"), is(0.0));
+ assertThat(getValue("hikaricp_pending_threads", "noConnection"), is(0.0));
+ assertThat(getValue("hikaricp_connections", "noConnection"), is(0.0));
+ }
+ finally {
+ StubConnection.slowCreate = false;
+ }
+ }
+
+ @Test
+ public void noConnectionWithoutPoolName() throws Exception {
+ HikariConfig config = new HikariConfig();
+ config.setMinimumIdle(0);
+ config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ StubConnection.slowCreate = true;
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ assertThat(getValue("hikaricp_active_connections", "HikariPool-1"), is(0.0));
+ assertThat(getValue("hikaricp_idle_connections", "HikariPool-1"), is(0.0));
+ assertThat(getValue("hikaricp_pending_threads", "HikariPool-1"), is(0.0));
+ assertThat(getValue("hikaricp_connections", "HikariPool-1"), is(0.0));
+ }
+ finally {
+ StubConnection.slowCreate = false;
+ }
+ }
+
+ @Test
+ public void connection1() throws Exception {
+ HikariConfig config = newHikariConfig();
+ config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+ config.setMaximumPoolSize(1);
+
+ StubConnection.slowCreate = true;
+ try (HikariDataSource ds = new HikariDataSource(config);
+ Connection connection1 = ds.getConnection()) {
+
+ quietlySleep(1000);
+
+ assertThat(getValue("hikaricp_active_connections", "connection1"), is(1.0));
+ assertThat(getValue("hikaricp_idle_connections", "connection1"), is(0.0));
+ assertThat(getValue("hikaricp_pending_threads", "connection1"), is(0.0));
+ assertThat(getValue("hikaricp_connections", "connection1"), is(1.0));
+ }
+ finally {
+ StubConnection.slowCreate = false;
+ }
+ }
+
+ @Test
+ public void connectionClosed() throws Exception {
+ HikariConfig config = newHikariConfig();
+ config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+ config.setMaximumPoolSize(1);
+
+ StubConnection.slowCreate = true;
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ try (Connection connection1 = ds.getConnection()) {
+ // close immediately
+ }
+
+ assertThat(getValue("hikaricp_active_connections", "connectionClosed"), is(0.0));
+ assertThat(getValue("hikaricp_idle_connections", "connectionClosed"), is(1.0));
+ assertThat(getValue("hikaricp_pending_threads", "connectionClosed"), is(0.0));
+ assertThat(getValue("hikaricp_connections", "connectionClosed"), is(1.0));
+ }
+ finally {
+ StubConnection.slowCreate = false;
+ }
+ }
+
+ private double getValue(String name, String poolName) {
+ String[] labelNames = {"pool"};
+ String[] labelValues = {poolName};
+ return CollectorRegistry.defaultRegistry.getSampleValue(name, labelNames, labelValues);
+ }
+
+}
diff --git a/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java b/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java
new file mode 100644
index 0000000..a7b0b03
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.metrics.prometheus;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.sql.Connection;
+import java.sql.SQLTransientConnectionException;
+
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+import io.prometheus.client.CollectorRegistry;
+
+public class PrometheusMetricsTrackerTest {
+ @Test
+ public void recordConnectionTimeout() throws Exception {
+ HikariConfig config = newHikariConfig();
+ config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
+ config.setJdbcUrl("jdbc:h2:mem:");
+ config.setMaximumPoolSize(2);
+ config.setConnectionTimeout(250);
+
+ String[] labelNames = {"pool"};
+ String[] labelValues = {config.getPoolName()};
+
+ try (HikariDataSource hikariDataSource = new HikariDataSource(config)) {
+ try (Connection connection1 = hikariDataSource.getConnection();
+ Connection connection2 = hikariDataSource.getConnection()) {
+ try (Connection connection3 = hikariDataSource.getConnection()) {
+ }
+ catch (SQLTransientConnectionException ignored) {
+ }
+ }
+
+ assertThat(CollectorRegistry.defaultRegistry.getSampleValue(
+ "hikaricp_connection_timeout_count",
+ labelNames,
+ labelValues), is(1.0));
+ assertThat(CollectorRegistry.defaultRegistry.getSampleValue(
+ "hikaricp_connection_acquired_nanos_count",
+ labelNames,
+ labelValues), is(equalTo(2.0)));
+ assertTrue(CollectorRegistry.defaultRegistry.getSampleValue(
+ "hikaricp_connection_acquired_nanos_sum",
+ labelNames,
+ labelValues) > 0.0);
+ assertThat(CollectorRegistry.defaultRegistry.getSampleValue(
+ "hikaricp_connection_usage_millis_count",
+ labelNames,
+ labelValues), is(equalTo(2.0)));
+ assertTrue(CollectorRegistry.defaultRegistry.getSampleValue(
+ "hikaricp_connection_usage_millis_sum",
+ labelNames,
+ labelValues) > 0.0);
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/mocks/MockDataSource.java b/src/test/java/com/zaxxer/hikari/mocks/MockDataSource.java
new file mode 100644
index 0000000..558f9fb
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/mocks/MockDataSource.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.mocks;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.PrintWriter;
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Statement;
+import java.util.logging.Logger;
+
+import javax.sql.DataSource;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ *
+ * @author Brett Wooldridge
+ */
+public class MockDataSource implements DataSource
+{
+ @Override
+ public Connection getConnection() throws SQLException
+ {
+ return createMockConnection();
+ }
+
+ @Override
+ public Connection getConnection(String username, String password) throws SQLException
+ {
+ return getConnection();
+ }
+
+ @Override
+ public PrintWriter getLogWriter() throws SQLException
+ {
+ return null;
+ }
+
+ @Override
+ public void setLogWriter(PrintWriter out) throws SQLException
+ {
+ }
+
+ @Override
+ public void setLoginTimeout(int seconds) throws SQLException
+ {
+ }
+
+ @Override
+ public int getLoginTimeout() throws SQLException
+ {
+ return 0;
+ }
+
+ public Logger getParentLogger() throws SQLFeatureNotSupportedException
+ {
+ return null;
+ }
+
+ @Override
+ public <T> T unwrap(Class<T> iface) throws SQLException
+ {
+ return null;
+ }
+
+ @Override
+ public boolean isWrapperFor(Class<?> iface) throws SQLException
+ {
+ return false;
+ }
+
+ public static Connection createMockConnection() throws SQLException {
+ // Setup mock connection
+ final Connection mockConnection = mock(Connection.class);
+
+ // Autocommit is always true by default
+ when(mockConnection.getAutoCommit()).thenReturn(true);
+
+ // Handle Connection.createStatement()
+ Statement statement = mock(Statement.class);
+ when(mockConnection.createStatement()).thenReturn(statement);
+ when(mockConnection.createStatement(anyInt(), anyInt())).thenReturn(statement);
+ when(mockConnection.createStatement(anyInt(), anyInt(), anyInt())).thenReturn(statement);
+ when(mockConnection.isValid(anyInt())).thenReturn(true);
+
+ // Handle Connection.prepareStatement()
+ PreparedStatement mockPreparedStatement = mock(PreparedStatement.class);
+ when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement);
+ when(mockConnection.prepareStatement(anyString(), anyInt())).thenReturn(mockPreparedStatement);
+ when(mockConnection.prepareStatement(anyString(), any(int[].class))).thenReturn(mockPreparedStatement);
+ when(mockConnection.prepareStatement(anyString(), any(String[].class))).thenReturn(mockPreparedStatement);
+ when(mockConnection.prepareStatement(anyString(), anyInt(), anyInt())).thenReturn(mockPreparedStatement);
+ when(mockConnection.prepareStatement(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(mockPreparedStatement);
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable
+ {
+ return null;
+ }
+ }).doNothing().when(mockPreparedStatement).setInt(anyInt(), anyInt());
+
+ ResultSet mockResultSet = mock(ResultSet.class);
+ when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet);
+ when(mockResultSet.getString(anyInt())).thenReturn("aString");
+ when(mockResultSet.next()).thenReturn(true);
+
+ // Handle Connection.prepareCall()
+ CallableStatement mockCallableStatement = mock(CallableStatement.class);
+ when(mockConnection.prepareCall(anyString())).thenReturn(mockCallableStatement);
+ when(mockConnection.prepareCall(anyString(), anyInt(), anyInt())).thenReturn(mockCallableStatement);
+ when(mockConnection.prepareCall(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(mockCallableStatement);
+
+ // Handle Connection.close()
+// doAnswer(new Answer<Void>() {
+// public Void answer(InvocationOnMock invocation) throws Throwable {
+// return null;
+// }
+// }).doThrow(new SQLException("Connection is already closed")).when(mockConnection).close();
+
+ // Handle Connection.commit()
+// doAnswer(new Answer<Void>() {
+// public Void answer(InvocationOnMock invocation) throws Throwable {
+// return null;
+// }
+// }).doThrow(new SQLException("Transaction already commited")).when(mockConnection).commit();
+
+ // Handle Connection.rollback()
+// doAnswer(new Answer<Void>() {
+// public Void answer(InvocationOnMock invocation) throws Throwable {
+// return null;
+// }
+// }).doThrow(new SQLException("Transaction already rolledback")).when(mockConnection).rollback();
+
+ return mockConnection;
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubBaseConnection.java b/src/test/java/com/zaxxer/hikari/mocks/StubBaseConnection.java
new file mode 100644
index 0000000..f88705e
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/mocks/StubBaseConnection.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.mocks;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public abstract class StubBaseConnection implements Connection
+{
+ public volatile boolean throwException;
+
+ /** {@inheritDoc} */
+ @Override
+ public Statement createStatement() throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ return new StubStatement(this);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public PreparedStatement prepareStatement(String sql) throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ return new StubPreparedStatement(this);
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubConnection.java b/src/test/java/com/zaxxer/hikari/mocks/StubConnection.java
new file mode 100644
index 0000000..f96624b
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/mocks/StubConnection.java
@@ -0,0 +1,486 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.mocks;
+
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.CallableStatement;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.NClob;
+import java.sql.PreparedStatement;
+import java.sql.SQLClientInfoException;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.sql.Struct;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.zaxxer.hikari.util.UtilityElf;
+
+/**
+ *
+ * @author Brett Wooldridge
+ */
+public class StubConnection extends StubBaseConnection implements Connection
+{
+ public static final AtomicInteger count = new AtomicInteger();
+ public static volatile boolean slowCreate;
+ public static volatile boolean oldDriver;
+
+ private static long foo;
+ private boolean autoCommit;
+ private int isolation = Connection.TRANSACTION_READ_COMMITTED;
+ private String catalog;
+
+ static {
+ foo = System.currentTimeMillis();
+ }
+
+ public StubConnection() {
+ count.incrementAndGet();
+ if (slowCreate) {
+ UtilityElf.quietlySleep(1000);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T unwrap(Class<T> iface) throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+
+ if (iface.isInstance(this)) {
+ return (T) this;
+ }
+
+ throw new SQLException("Wrapped connection is not an instance of " + iface);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isWrapperFor(Class<?> iface) throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public CallableStatement prepareCall(String sql) throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String nativeSQL(String sql) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setAutoCommit(boolean autoCommit) throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ this.autoCommit = autoCommit;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean getAutoCommit() throws SQLException
+ {
+ return autoCommit;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void commit() throws SQLException
+ {
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void rollback() throws SQLException
+ {
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void close() throws SQLException
+ {
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isClosed() throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public DatabaseMetaData getMetaData() throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setReadOnly(boolean readOnly) throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isReadOnly() throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setCatalog(String catalog) throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ this.catalog = catalog;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getCatalog() throws SQLException
+ {
+ return catalog;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setTransactionIsolation(int level) throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ this.isolation = level;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getTransactionIsolation() throws SQLException
+ {
+ return isolation;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public SQLWarning getWarnings() throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void clearWarnings() throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ return new StubPreparedStatement(this);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Map<String, Class<?>> getTypeMap() throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setTypeMap(Map<String, Class<?>> map) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setHoldability(int holdability) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getHoldability() throws SQLException
+ {
+ return (int) foo;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Savepoint setSavepoint() throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Savepoint setSavepoint(String name) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void rollback(Savepoint savepoint) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void releaseSavepoint(Savepoint savepoint) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ return new StubPreparedStatement(this);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ return new StubPreparedStatement(this);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ return new StubPreparedStatement(this);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ return new StubPreparedStatement(this);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Clob createClob() throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Blob createBlob() throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public NClob createNClob() throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public SQLXML createSQLXML() throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isValid(int timeout) throws SQLException
+ {
+ if (throwException) {
+ throw new SQLException();
+ }
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setClientInfo(String name, String value) throws SQLClientInfoException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setClientInfo(Properties properties) throws SQLClientInfoException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getClientInfo(String name) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Properties getClientInfo() throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Array createArrayOf(String typeName, Object[] elements) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Struct createStruct(String typeName, Object[] attributes) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public void setSchema(String schema) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ public String getSchema() throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public void abort(Executor executor) throws SQLException
+ {
+ throw new SQLException("Intentional exception during abort");
+ }
+
+ /** {@inheritDoc} */
+ public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ public int getNetworkTimeout() throws SQLException
+ {
+ if (oldDriver) {
+ throw new AbstractMethodError();
+ }
+
+ return 0;
+ }
+
+}
diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubDataSource.java b/src/test/java/com/zaxxer/hikari/mocks/StubDataSource.java
new file mode 100755
index 0000000..c7da189
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/mocks/StubDataSource.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.mocks;
+
+import com.zaxxer.hikari.util.UtilityElf;
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.logging.Logger;
+
+import javax.sql.DataSource;
+
+/**
+ *
+ * @author Brett Wooldridge
+ */
+public class StubDataSource implements DataSource
+{
+ private String user;
+ private String password;
+ private PrintWriter logWriter;
+ private SQLException throwException;
+ private long connectionAcquistionTime = 0;
+ private int loginTimeout;
+
+ public String getUser()
+ {
+ return user;
+ }
+
+ public void setUser(String user)
+ {
+ this.user = user;
+ }
+
+ public String getPassword()
+ {
+ return password;
+ }
+
+ public void setPassword(String password)
+ {
+ this.password = password;
+ }
+
+ public void setURL(String url)
+ {
+ // we don't care
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public PrintWriter getLogWriter() throws SQLException
+ {
+ return logWriter;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setLogWriter(PrintWriter out) throws SQLException
+ {
+ this.logWriter = out;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setLoginTimeout(int seconds) throws SQLException
+ {
+ this.loginTimeout = seconds;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getLoginTimeout() throws SQLException
+ {
+ return loginTimeout;
+ }
+
+ /** {@inheritDoc} */
+ public Logger getParentLogger() throws SQLFeatureNotSupportedException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T unwrap(Class<T> iface) throws SQLException
+ {
+ if (iface.isInstance(this)) {
+ return (T) this;
+ }
+
+ throw new SQLException("Wrapped DataSource is not an instance of " + iface);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isWrapperFor(Class<?> iface) throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Connection getConnection() throws SQLException
+ {
+ if (throwException != null) {
+ throw throwException;
+ }
+ if (connectionAcquistionTime > 0) {
+ UtilityElf.quietlySleep(connectionAcquistionTime);
+ }
+
+ return new StubConnection();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Connection getConnection(String username, String password) throws SQLException
+ {
+ return new StubConnection();
+ }
+
+ public void setThrowException(SQLException e)
+ {
+ this.throwException = e;
+ }
+
+ public void setConnectionAcquistionTime(long connectionAcquisitionTime) {
+ this.connectionAcquistionTime = connectionAcquisitionTime;
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubDriver.java b/src/test/java/com/zaxxer/hikari/mocks/StubDriver.java
new file mode 100644
index 0000000..eff7292
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/mocks/StubDriver.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.mocks;
+
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.DriverPropertyInfo;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Brett Wooldridge
+ */
+public class StubDriver implements Driver
+{
+ private static final Driver driver;
+
+ static
+ {
+ driver = new StubDriver();
+ try
+ {
+ DriverManager.registerDriver(driver);
+ }
+ catch (SQLException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Connection connect(String url, Properties info) throws SQLException
+ {
+ return new StubConnection();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean acceptsURL(String url) throws SQLException
+ {
+ return "jdbc:stub".equals(url);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getMajorVersion()
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getMinorVersion()
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean jdbcCompliant()
+ {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public Logger getParentLogger() throws SQLFeatureNotSupportedException
+ {
+ return null;
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java b/src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java
new file mode 100644
index 0000000..5c48f85
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java
@@ -0,0 +1,671 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.mocks;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.NClob;
+import java.sql.ParameterMetaData;
+import java.sql.PreparedStatement;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.RowId;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Calendar;
+
+/**
+ *
+ * @author Brett Wooldridge
+ */
+public class StubPreparedStatement extends StubStatement implements PreparedStatement
+{
+ public StubPreparedStatement(Connection connection)
+ {
+ super(connection);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ResultSet executeQuery(String sql) throws SQLException
+ {
+ return new StubResultSet();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int executeUpdate(String sql) throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getMaxFieldSize() throws SQLException
+ {
+ throw new SQLException("Simulated disconnection error", "08999");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setMaxFieldSize(int max) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getMaxRows() throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setMaxRows(int max) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setEscapeProcessing(boolean enable) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getQueryTimeout() throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setQueryTimeout(int seconds) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void cancel() throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public SQLWarning getWarnings() throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void clearWarnings() throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setCursorName(String name) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean execute(String sql) throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ResultSet getResultSet() throws SQLException
+ {
+ return new StubResultSet();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getUpdateCount() throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean getMoreResults() throws SQLException
+ {
+ if (isClosed())
+ {
+ throw new SQLException("Connection is closed");
+ }
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setFetchDirection(int direction) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getFetchDirection() throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setFetchSize(int rows) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getFetchSize() throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getResultSetConcurrency() throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getResultSetType() throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void addBatch(String sql) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void clearBatch() throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int[] executeBatch() throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean getMoreResults(int current) throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ResultSet getGeneratedKeys() throws SQLException
+ {
+ return new StubResultSet();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int executeUpdate(String sql, int[] columnIndexes) throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int executeUpdate(String sql, String[] columnNames) throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean execute(String sql, int autoGeneratedKeys) throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean execute(String sql, int[] columnIndexes) throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean execute(String sql, String[] columnNames) throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getResultSetHoldability() throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setPoolable(boolean poolable) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isPoolable() throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void closeOnCompletion() throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isCloseOnCompletion() throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T unwrap(Class<T> iface) throws SQLException
+ {
+ if (iface.isInstance(this)) {
+ return (T) this;
+ }
+
+ throw new SQLException("Wrapped connection is not an instance of " + iface);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isWrapperFor(Class<?> iface) throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ResultSet executeQuery() throws SQLException
+ {
+ return new StubResultSet();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int executeUpdate() throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setNull(int parameterIndex, int sqlType) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setBoolean(int parameterIndex, boolean x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setByte(int parameterIndex, byte x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setShort(int parameterIndex, short x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setInt(int parameterIndex, int x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setLong(int parameterIndex, long x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setFloat(int parameterIndex, float x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setDouble(int parameterIndex, double x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setString(int parameterIndex, String x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setBytes(int parameterIndex, byte[] x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setDate(int parameterIndex, Date x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setTime(int parameterIndex, Time x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void clearParameters() throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setObject(int parameterIndex, Object x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean execute() throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void addBatch() throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setRef(int parameterIndex, Ref x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setBlob(int parameterIndex, Blob x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setClob(int parameterIndex, Clob x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setArray(int parameterIndex, Array x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ResultSetMetaData getMetaData() throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setURL(int parameterIndex, URL x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ParameterMetaData getParameterMetaData() throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setRowId(int parameterIndex, RowId x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setNString(int parameterIndex, String value) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setNClob(int parameterIndex, NClob value) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setClob(int parameterIndex, Reader reader, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setClob(int parameterIndex, Reader reader) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setNClob(int parameterIndex, Reader reader) throws SQLException
+ {
+ }
+
+}
diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubResultSet.java b/src/test/java/com/zaxxer/hikari/mocks/StubResultSet.java
new file mode 100644
index 0000000..911daba
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/mocks/StubResultSet.java
@@ -0,0 +1,1292 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.mocks;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Date;
+import java.sql.NClob;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.RowId;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Statement;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Calendar;
+import java.util.Map;
+
+/**
+ *
+ * @author Brett Wooldridge
+ */
+public class StubResultSet implements ResultSet
+{
+ private int counter;
+ private boolean closed;
+
+ /** {@inheritDoc} */
+ @Override
+ public <T> T unwrap(Class<T> iface) throws SQLException
+ {
+
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isWrapperFor(Class<?> iface) throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean next() throws SQLException
+ {
+ return (counter > 100000);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void close() throws SQLException
+ {
+ closed = true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean wasNull() throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getString(int columnIndex) throws SQLException
+ {
+ return "aString";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean getBoolean(int columnIndex) throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public byte getByte(int columnIndex) throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public short getShort(int columnIndex) throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getInt(int columnIndex) throws SQLException
+ {
+ return ++counter;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long getLong(int columnIndex) throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public float getFloat(int columnIndex) throws SQLException
+ {
+ throw new SQLException("Simulated disconnection error", "08999");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public double getDouble(int columnIndex) throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public byte[] getBytes(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Date getDate(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Time getTime(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Timestamp getTimestamp(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public InputStream getAsciiStream(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public InputStream getUnicodeStream(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public InputStream getBinaryStream(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getString(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean getBoolean(String columnLabel) throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public byte getByte(String columnLabel) throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public short getShort(String columnLabel) throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getInt(String columnLabel) throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public long getLong(String columnLabel) throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public float getFloat(String columnLabel) throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public double getDouble(String columnLabel) throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public byte[] getBytes(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Date getDate(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Time getTime(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Timestamp getTimestamp(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public InputStream getAsciiStream(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public InputStream getUnicodeStream(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public InputStream getBinaryStream(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public SQLWarning getWarnings() throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void clearWarnings() throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getCursorName() throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ResultSetMetaData getMetaData() throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object getObject(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object getObject(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int findColumn(String columnLabel) throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Reader getCharacterStream(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Reader getCharacterStream(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public BigDecimal getBigDecimal(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public BigDecimal getBigDecimal(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isBeforeFirst() throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isAfterLast() throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isFirst() throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isLast() throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void beforeFirst() throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void afterLast() throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean first() throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean last() throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getRow() throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean absolute(int row) throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean relative(int rows) throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean previous() throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setFetchDirection(int direction) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getFetchDirection() throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setFetchSize(int rows) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getFetchSize() throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getType() throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getConcurrency() throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean rowUpdated() throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean rowInserted() throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean rowDeleted() throws SQLException
+ {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateNull(int columnIndex) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateBoolean(int columnIndex, boolean x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateByte(int columnIndex, byte x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateShort(int columnIndex, short x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateInt(int columnIndex, int x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateLong(int columnIndex, long x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateFloat(int columnIndex, float x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateDouble(int columnIndex, double x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateString(int columnIndex, String x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateBytes(int columnIndex, byte[] x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateDate(int columnIndex, Date x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateTime(int columnIndex, Time x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateObject(int columnIndex, Object x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateNull(String columnLabel) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateBoolean(String columnLabel, boolean x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateByte(String columnLabel, byte x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateShort(String columnLabel, short x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateInt(String columnLabel, int x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateLong(String columnLabel, long x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateFloat(String columnLabel, float x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateDouble(String columnLabel, double x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateString(String columnLabel, String x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateBytes(String columnLabel, byte[] x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateDate(String columnLabel, Date x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateTime(String columnLabel, Time x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateObject(String columnLabel, Object x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void insertRow() throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateRow() throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void deleteRow() throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void refreshRow() throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void cancelRowUpdates() throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void moveToInsertRow() throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void moveToCurrentRow() throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Statement getStatement() throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object getObject(int columnIndex, Map<String, Class<?>> map) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Ref getRef(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Blob getBlob(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Clob getClob(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Array getArray(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object getObject(String columnLabel, Map<String, Class<?>> map) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Ref getRef(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Blob getBlob(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Clob getClob(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Array getArray(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Date getDate(int columnIndex, Calendar cal) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Date getDate(String columnLabel, Calendar cal) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Time getTime(int columnIndex, Calendar cal) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Time getTime(String columnLabel, Calendar cal) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public URL getURL(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public URL getURL(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateRef(int columnIndex, Ref x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateRef(String columnLabel, Ref x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateBlob(int columnIndex, Blob x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateBlob(String columnLabel, Blob x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateClob(int columnIndex, Clob x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateClob(String columnLabel, Clob x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateArray(int columnIndex, Array x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateArray(String columnLabel, Array x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public RowId getRowId(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public RowId getRowId(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateRowId(int columnIndex, RowId x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateRowId(String columnLabel, RowId x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getHoldability() throws SQLException
+ {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isClosed() throws SQLException
+ {
+ return closed;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateNString(int columnIndex, String nString) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateNString(String columnLabel, String nString) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateNClob(int columnIndex, NClob nClob) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateNClob(String columnLabel, NClob nClob) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public NClob getNClob(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public NClob getNClob(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public SQLXML getSQLXML(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public SQLXML getSQLXML(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getNString(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getNString(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Reader getNCharacterStream(int columnIndex) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Reader getNCharacterStream(String columnLabel) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateClob(int columnIndex, Reader reader, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateClob(String columnLabel, Reader reader, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateCharacterStream(int columnIndex, Reader x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateClob(int columnIndex, Reader reader) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateClob(String columnLabel, Reader reader) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateNClob(int columnIndex, Reader reader) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateNClob(String columnLabel, Reader reader) throws SQLException
+ {
+ }
+
+ /** {@inheritDoc} */
+ public <T> T getObject(int columnIndex, Class<T> type) throws SQLException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public <T> T getObject(String columnLabel, Class<T> type) throws SQLException
+ {
+ return null;
+ }
+
+}
diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubStatement.java b/src/test/java/com/zaxxer/hikari/mocks/StubStatement.java
new file mode 100644
index 0000000..73fec69
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/mocks/StubStatement.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.mocks;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.SQLWarning;
+import java.sql.Statement;
+
+/**
+ *
+ * @author Brett Wooldridge
+ */
+public class StubStatement implements Statement
+{
+ public static volatile boolean oldDriver;
+ private boolean closed;
+ private Connection connection;
+
+ public StubStatement(Connection connection) {
+ this.connection = connection;
+ }
+
+ /** {@inheritDoc} */
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T unwrap(Class<T> iface) throws SQLException
+ {
+ checkClosed();
+ return (T) this;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isWrapperFor(Class<?> iface) throws SQLException
+ {
+ checkClosed();
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ResultSet executeQuery(String sql) throws SQLException
+ {
+ checkClosed();
+ StubResultSet resultSet = new StubResultSet();
+ return resultSet;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int executeUpdate(String sql) throws SQLException
+ {
+ checkClosed();
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void close() throws SQLException
+ {
+ closed = true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getMaxFieldSize() throws SQLException
+ {
+ checkClosed();
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setMaxFieldSize(int max) throws SQLException
+ {
+ checkClosed();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getMaxRows() throws SQLException
+ {
+ checkClosed();
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setMaxRows(int max) throws SQLException
+ {
+ checkClosed();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setEscapeProcessing(boolean enable) throws SQLException
+ {
+ checkClosed();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getQueryTimeout() throws SQLException
+ {
+ checkClosed();
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setQueryTimeout(int seconds) throws SQLException
+ {
+ if (oldDriver) {
+ throw new SQLFeatureNotSupportedException();
+ }
+
+ checkClosed();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void cancel() throws SQLException
+ {
+ checkClosed();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public SQLWarning getWarnings() throws SQLException
+ {
+ checkClosed();
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void clearWarnings() throws SQLException
+ {
+ checkClosed();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setCursorName(String name) throws SQLException
+ {
+ checkClosed();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean execute(String sql) throws SQLException
+ {
+ checkClosed();
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ResultSet getResultSet() throws SQLException
+ {
+ checkClosed();
+ return new StubResultSet();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getUpdateCount() throws SQLException
+ {
+ checkClosed();
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean getMoreResults() throws SQLException
+ {
+ checkClosed();
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setFetchDirection(int direction) throws SQLException
+ {
+ checkClosed();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getFetchDirection() throws SQLException
+ {
+ checkClosed();
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setFetchSize(int rows) throws SQLException
+ {
+ checkClosed();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getFetchSize() throws SQLException
+ {
+ checkClosed();
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getResultSetConcurrency() throws SQLException
+ {
+ checkClosed();
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getResultSetType() throws SQLException
+ {
+ checkClosed();
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void addBatch(String sql) throws SQLException
+ {
+ checkClosed();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void clearBatch() throws SQLException
+ {
+ checkClosed();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int[] executeBatch() throws SQLException
+ {
+ checkClosed();
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Connection getConnection() throws SQLException
+ {
+ checkClosed();
+ return connection;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean getMoreResults(int current) throws SQLException
+ {
+ checkClosed();
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ResultSet getGeneratedKeys() throws SQLException
+ {
+ checkClosed();
+ return new StubResultSet();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException
+ {
+ checkClosed();
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int executeUpdate(String sql, int[] columnIndexes) throws SQLException
+ {
+ checkClosed();
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int executeUpdate(String sql, String[] columnNames) throws SQLException
+ {
+ checkClosed();
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean execute(String sql, int autoGeneratedKeys) throws SQLException
+ {
+ checkClosed();
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean execute(String sql, int[] columnIndexes) throws SQLException
+ {
+ checkClosed();
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean execute(String sql, String[] columnNames) throws SQLException
+ {
+ checkClosed();
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getResultSetHoldability() throws SQLException
+ {
+ checkClosed();
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isClosed() throws SQLException
+ {
+ return closed;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setPoolable(boolean poolable) throws SQLException
+ {
+ checkClosed();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isPoolable() throws SQLException
+ {
+ checkClosed();
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public void closeOnCompletion() throws SQLException
+ {
+ checkClosed();
+ }
+
+ /** {@inheritDoc} */
+ public boolean isCloseOnCompletion() throws SQLException
+ {
+ checkClosed();
+ return false;
+ }
+
+ private void checkClosed() throws SQLException
+ {
+ if (closed) {
+ throw new SQLException("Statement is closed");
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java b/src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java
new file mode 100644
index 0000000..761a802
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.zaxxer.hikari.osgi;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+import javax.inject.Inject;
+
+import java.io.File;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.ops4j.pax.exam.CoreOptions.*;
+
+/**
+ * @author lburgazzoli
+ */
+@RunWith(PaxExam.class)
+public class OSGiBundleTest
+{
+ @Inject
+ BundleContext context;
+
+ @Configuration
+ public Option[] config()
+ {
+ return options(
+ systemProperty("org.osgi.framework.storage.clean").value("true"),
+ systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("WARN"),
+ mavenBundle("org.slf4j","slf4j-api","1.7.5"),
+ mavenBundle("org.slf4j","slf4j-simple","1.7.5").noStart(),
+ mavenBundle("org.javassist", "javassist", "3.19.0-GA"),
+ new File("target/classes").exists()
+ ? bundle("reference:file:target/classes")
+ : bundle("reference:file:../target/classes"),
+ junitBundles(),
+ cleanCaches()
+ );
+ }
+
+ @Test
+ public void checkInject()
+ {
+ assertNotNull(context);
+ }
+
+ @Test
+ public void checkBundle()
+ {
+ Boolean bundleFound = false;
+ Boolean bundleActive = false;
+
+ Bundle[] bundles = context.getBundles();
+ for(Bundle bundle : bundles)
+ {
+ if(bundle != null)
+ {
+ if(bundle.getSymbolicName().equals("com.zaxxer.HikariCP"))
+ {
+ bundleFound = true;
+ if(bundle.getState() == Bundle.ACTIVE)
+ {
+ bundleActive = true;
+ }
+ }
+ }
+ }
+
+ assertTrue(bundleFound);
+ assertTrue(bundleActive);
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/ConcurrentCloseConnectionTest.java b/src/test/java/com/zaxxer/hikari/pool/ConcurrentCloseConnectionTest.java
new file mode 100644
index 0000000..8820b79
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/ConcurrentCloseConnectionTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+/**
+ * @author Matthew Tambara (matthew.tambara@liferay.com)
+ */
+public class ConcurrentCloseConnectionTest
+{
+ @Test
+ public void testConcurrentClose() throws Exception
+ {
+ HikariConfig config = newHikariConfig();
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config);
+ final Connection connection = ds.getConnection()) {
+
+ ExecutorService executorService = Executors.newFixedThreadPool(10);
+
+ List<Future<?>> futures = new ArrayList<>();
+
+ for (int i = 0; i < 500; i++) {
+ final PreparedStatement preparedStatement =
+ connection.prepareStatement("");
+
+ futures.add(executorService.submit(new Callable<Void>() {
+
+ @Override
+ public Void call() throws Exception {
+ preparedStatement.close();
+
+ return null;
+ }
+
+ }));
+ }
+
+ executorService.shutdown();
+
+ for (Future<?> future : futures) {
+ future.get();
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java b/src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java
new file mode 100755
index 0000000..e66ba58
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2013, 2017 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.getPool;
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.util.ClockSource.currentTime;
+import static com.zaxxer.hikari.util.ClockSource.elapsedMillis;
+import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
+import static java.util.concurrent.Executors.newFixedThreadPool;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.sql.Connection;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.mocks.StubDataSource;
+
+/**
+ * @author Matthew Tambara (matthew.tambara@liferay.com)
+ */
+public class ConnectionPoolSizeVsThreadsTest {
+
+ public static final Logger LOGGER = LoggerFactory.getLogger(ConnectionPoolSizeVsThreadsTest.class);
+
+ public static final int ITERATIONS = 50_000;
+
+ @Test
+ public void testPoolSizeAboutSameSizeAsThreadCount() throws Exception {
+ final int threadCount = 50;
+ final Counts counts = testPoolSize(2 /*minIdle*/,
+ 100 /*maxPoolSize*/,
+ threadCount,
+ 1 /*workTimeMs*/,
+ 0 /*restTimeMs*/,
+ 20 /*connectionAcquisitionTimeMs*/,
+ ITERATIONS,
+ SECONDS.toMillis(2) /*postTestTimeMs*/);
+
+ // maxActive may never make it to threadCount but it shouldn't be any higher
+ assertEquals(threadCount, counts.maxActive, 15 /*delta*/);
+ assertEquals(threadCount, counts.maxTotal, 5 /*delta*/);
+ }
+
+ @Test
+ public void testSlowConnectionTimeBurstyWork() throws Exception {
+ // setup a bursty work load, 50 threads all needing to do around 100 units of work.
+ // Using a more realistic time for connection startup of 250 ms and only 5 seconds worth of work will mean that we end up finishing
+ // all of the work before we actually have setup 50 connections even though we have requested 50 connections
+ final int threadCount = 50;
+ final int workItems = threadCount * 100;
+ final int workTimeMs = 0;
+ final int connectionAcquisitionTimeMs = 250;
+ final Counts counts = testPoolSize(2 /*minIdle*/,
+ 100 /*maxPoolSize*/,
+ threadCount,
+ workTimeMs,
+ 0 /*restTimeMs*/,
+ connectionAcquisitionTimeMs,
+ workItems /*iterations*/,
+ SECONDS.toMillis(3) /*postTestTimeMs*/);
+
+ // hard to put exact bounds on how many thread we will use but we can put an upper bound on usage (if there was only one thread)
+ final long totalWorkTime = workItems * workTimeMs;
+ final long connectionMax = totalWorkTime / connectionAcquisitionTimeMs;
+ assertTrue(connectionMax <= counts.maxActive);
+ assertEquals(connectionMax, counts.maxTotal, 2 + 2 /*delta*/);
+ }
+
+ private Counts testPoolSize(final int minIdle, final int maxPoolSize, final int threadCount,
+ final long workTimeMs, final long restTimeMs, final long connectionAcquisitionTimeMs,
+ final int iterations, final long postTestTimeMs) throws Exception {
+
+ LOGGER.info("Starting test (minIdle={}, maxPoolSize={}, threadCount={}, workTimeMs={}, restTimeMs={}, connectionAcquisitionTimeMs={}, iterations={}, postTestTimeMs={})",
+ minIdle, maxPoolSize, threadCount, workTimeMs, restTimeMs, connectionAcquisitionTimeMs, iterations, postTestTimeMs);
+
+ final HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(minIdle);
+ config.setMaximumPoolSize(maxPoolSize);
+ config.setInitializationFailTimeout(Long.MAX_VALUE);
+ config.setConnectionTimeout(2500);
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ final AtomicReference<Exception> ref = new AtomicReference<>(null);
+
+ // Initialize HikariPool with no initial connections and room to grow
+ try (final HikariDataSource ds = new HikariDataSource(config)) {
+ final StubDataSource stubDataSource = ds.unwrap(StubDataSource.class);
+ // connection acquisition takes more than 0 ms in a real system
+ stubDataSource.setConnectionAcquistionTime(connectionAcquisitionTimeMs);
+
+ final ExecutorService threadPool = newFixedThreadPool(threadCount);
+ final CountDownLatch allThreadsDone = new CountDownLatch(iterations);
+ for (int i = 0; i < iterations; i++) {
+ threadPool.submit(() -> {
+ if (ref.get() == null) {
+ quietlySleep(restTimeMs);
+ try (Connection c2 = ds.getConnection()) {
+ quietlySleep(workTimeMs);
+ }
+ catch (Exception e) {
+ ref.set(e);
+ }
+ }
+ allThreadsDone.countDown();
+ });
+ }
+
+ final HikariPool pool = getPool(ds);
+
+ // collect pool usage data while work is still being done
+ final Counts underLoad = new Counts();
+ while (allThreadsDone.getCount() > 0 || pool.getTotalConnections() < minIdle) {
+ quietlySleep(50);
+ underLoad.updateMaxCounts(pool);
+ }
+
+ // wait for long enough any pending acquisitions have already been done
+ LOGGER.info("Test Over, waiting for post delay time {}ms ", postTestTimeMs);
+ quietlySleep(connectionAcquisitionTimeMs + workTimeMs + restTimeMs);
+
+ // collect pool data while there is no work to do.
+ final Counts postLoad = new Counts();
+ final long start = currentTime();
+ while (elapsedMillis(start) < postTestTimeMs) {
+ quietlySleep(50);
+ postLoad.updateMaxCounts(pool);
+ }
+
+ allThreadsDone.await();
+
+ threadPool.shutdown();
+ threadPool.awaitTermination(30, SECONDS);
+
+ if (ref.get() != null) {
+ LOGGER.error("Task failed", ref.get());
+ fail("Task failed");
+ }
+
+ LOGGER.info("Under Load... {}", underLoad);
+ LOGGER.info("Post Load.... {}", postLoad);
+
+ // verify that the no connections created after the work has stopped
+ if (postTestTimeMs > 0) {
+ if (postLoad.maxActive != 0) {
+ fail("Max Active was greater than 0 after test was done");
+ }
+
+ final int createdAfterWorkAllFinished = postLoad.maxTotal - underLoad.maxTotal;
+ assertEquals("Connections were created when there was no waiting consumers", 0, createdAfterWorkAllFinished, 1 /*delta*/);
+ }
+
+ return underLoad;
+ }
+ }
+
+ private static class Counts {
+ int maxTotal = 0;
+ int maxActive = 0;
+
+ void updateMaxCounts(final HikariPool pool) {
+ maxTotal = Math.max(pool.getTotalConnections(), maxTotal);
+ maxActive = Math.max(pool.getActiveConnections(), maxActive);
+ }
+
+ @Override
+ public String toString() {
+ return "Counts{" +
+ "maxTotal=" + maxTotal +
+ ", maxActive=" + maxActive +
+ '}';
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java b/src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java
new file mode 100755
index 0000000..31f8363
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.setSlf4jLogLevel;
+import static org.junit.Assert.fail;
+
+import java.sql.Connection;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.logging.log4j.Level;
+import org.junit.After;
+import org.junit.Test;
+import org.slf4j.LoggerFactory;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.util.ConcurrentBag;
+
+/**
+ * @author Matthew Tambara (matthew.tambara@liferay.com)
+ */
+public class ConnectionRaceConditionTest
+{
+
+ public static final int ITERATIONS = 10_000;
+
+ @Test
+ public void testRaceCondition() throws Exception
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(10);
+ config.setInitializationFailTimeout(Long.MAX_VALUE);
+ config.setConnectionTimeout(2500);
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ setSlf4jLogLevel(ConcurrentBag.class, Level.INFO);
+
+ final AtomicReference<Exception> ref = new AtomicReference<>(null);
+
+ // Initialize HikariPool with no initial connections and room to grow
+ try (final HikariDataSource ds = new HikariDataSource(config)) {
+ ExecutorService threadPool = Executors.newFixedThreadPool(2);
+ for (int i = 0; i < ITERATIONS; i++) {
+ threadPool.submit(new Callable<Exception>() {
+ /** {@inheritDoc} */
+ @Override
+ public Exception call() throws Exception
+ {
+ if (ref.get() == null) {
+ Connection c2;
+ try {
+ c2 = ds.getConnection();
+ ds.evictConnection(c2);
+ }
+ catch (Exception e) {
+ ref.set(e);
+ }
+ }
+ return null;
+ }
+ });
+ }
+
+ threadPool.shutdown();
+ threadPool.awaitTermination(30, TimeUnit.SECONDS);
+
+ if (ref.get() != null) {
+ LoggerFactory.getLogger(ConnectionRaceConditionTest.class).error("Task failed", ref.get());
+ fail("Task failed");
+ }
+ }
+ catch (Exception e) {
+ throw e;
+ }
+ }
+
+ @After
+ public void after()
+ {
+ System.getProperties().remove("com.zaxxer.hikari.housekeeping.periodMs");
+
+ setSlf4jLogLevel(HikariPool.class, Level.WARN);
+ setSlf4jLogLevel(ConcurrentBag.class, Level.WARN);
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/ConnectionStateTest.java b/src/test/java/com/zaxxer/hikari/pool/ConnectionStateTest.java
new file mode 100644
index 0000000..35b36fb
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/ConnectionStateTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.util.UtilityElf;
+
+public class ConnectionStateTest
+{
+ @Test
+ public void testAutoCommit() throws SQLException
+ {
+ try (HikariDataSource ds = newHikariDataSource()) {
+ ds.setAutoCommit(true);
+ ds.setMinimumIdle(1);
+ ds.setMaximumPoolSize(1);
+ ds.setConnectionTestQuery("VALUES 1");
+ ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+ ds.addDataSourceProperty("user", "bar");
+ ds.addDataSourceProperty("password", "secret");
+ ds.addDataSourceProperty("url", "baf");
+ ds.addDataSourceProperty("loginTimeout", "10");
+
+ try (Connection connection = ds.getConnection()) {
+ Connection unwrap = connection.unwrap(Connection.class);
+ connection.setAutoCommit(false);
+ connection.close();
+
+ assertTrue(unwrap.getAutoCommit());
+ }
+ }
+ }
+
+ @Test
+ public void testTransactionIsolation() throws SQLException
+ {
+ try (HikariDataSource ds = newHikariDataSource()) {
+ ds.setTransactionIsolation("TRANSACTION_READ_COMMITTED");
+ ds.setMinimumIdle(1);
+ ds.setMaximumPoolSize(1);
+ ds.setConnectionTestQuery("VALUES 1");
+ ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (Connection connection = ds.getConnection()) {
+ Connection unwrap = connection.unwrap(Connection.class);
+ connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
+ connection.close();
+
+ assertEquals(Connection.TRANSACTION_READ_COMMITTED, unwrap.getTransactionIsolation());
+ }
+ }
+ }
+
+ @Test
+ public void testIsolation() throws Exception
+ {
+ HikariConfig config = newHikariConfig();
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+ config.setTransactionIsolation("TRANSACTION_REPEATABLE_READ");
+ config.validate();
+
+ int transactionIsolation = UtilityElf.getTransactionIsolation(config.getTransactionIsolation());
+ assertSame(Connection.TRANSACTION_REPEATABLE_READ, transactionIsolation);
+ }
+
+ @Test
+ public void testReadOnly() throws Exception
+ {
+ try (HikariDataSource ds = newHikariDataSource()) {
+ ds.setCatalog("test");
+ ds.setMinimumIdle(1);
+ ds.setMaximumPoolSize(1);
+ ds.setConnectionTestQuery("VALUES 1");
+ ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (Connection connection = ds.getConnection()) {
+ Connection unwrap = connection.unwrap(Connection.class);
+ connection.setReadOnly(true);
+ connection.close();
+
+ assertFalse(unwrap.isReadOnly());
+ }
+ }
+ }
+
+ @Test
+ public void testCatalog() throws SQLException
+ {
+ try (HikariDataSource ds = newHikariDataSource()) {
+ ds.setCatalog("test");
+ ds.setMinimumIdle(1);
+ ds.setMaximumPoolSize(1);
+ ds.setConnectionTestQuery("VALUES 1");
+ ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (Connection connection = ds.getConnection()) {
+ Connection unwrap = connection.unwrap(Connection.class);
+ connection.setCatalog("other");
+ connection.close();
+
+ assertEquals("test", unwrap.getCatalog());
+ }
+ }
+ }
+
+ @Test
+ public void testCommitTracking() throws SQLException
+ {
+ try (HikariDataSource ds = newHikariDataSource()) {
+ ds.setAutoCommit(false);
+ ds.setMinimumIdle(1);
+ ds.setMaximumPoolSize(1);
+ ds.setConnectionTestQuery("VALUES 1");
+ ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (Connection connection = ds.getConnection()) {
+ Statement statement = connection.createStatement();
+ statement.execute("SELECT something");
+ assertTrue(TestElf.getConnectionCommitDirtyState(connection));
+
+ connection.commit();
+ assertFalse(TestElf.getConnectionCommitDirtyState(connection));
+
+ statement.execute("SELECT something", Statement.NO_GENERATED_KEYS);
+ assertTrue(TestElf.getConnectionCommitDirtyState(connection));
+
+ connection.rollback();
+ assertFalse(TestElf.getConnectionCommitDirtyState(connection));
+
+ ResultSet resultSet = statement.executeQuery("SELECT something");
+ assertTrue(TestElf.getConnectionCommitDirtyState(connection));
+
+ connection.rollback(null);
+ assertFalse(TestElf.getConnectionCommitDirtyState(connection));
+
+ resultSet.updateRow();
+ assertTrue(TestElf.getConnectionCommitDirtyState(connection));
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/ExceptionTest.java b/src/test/java/com/zaxxer/hikari/pool/ExceptionTest.java
new file mode 100644
index 0000000..8bd64b0
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/ExceptionTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.getPool;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+public class ExceptionTest
+{
+ private HikariDataSource ds;
+
+ @Before
+ public void setup()
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(1);
+ config.setMaximumPoolSize(2);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ ds = new HikariDataSource(config);
+ }
+
+ @After
+ public void teardown()
+ {
+ ds.close();
+ }
+
+ @Test
+ public void testException1() throws SQLException
+ {
+ try (Connection connection = ds.getConnection()) {
+ assertNotNull(connection);
+
+ PreparedStatement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?");
+ assertNotNull(statement);
+
+ ResultSet resultSet = statement.executeQuery();
+ assertNotNull(resultSet);
+
+ try {
+ statement.getMaxFieldSize();
+ fail();
+ }
+ catch (Exception e) {
+ assertSame(SQLException.class, e.getClass());
+ }
+ }
+
+ HikariPool pool = getPool(ds);
+ assertTrue("Total (3) connections not as expected", pool.getTotalConnections() >= 0);
+ assertTrue("Idle (3) connections not as expected", pool.getIdleConnections() >= 0);
+ }
+
+ @Test
+ public void testUseAfterStatementClose() throws SQLException
+ {
+ Connection connection = ds.getConnection();
+ assertNotNull(connection);
+
+ try (Statement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?")) {
+ statement.close();
+ statement.getMoreResults();
+
+ fail();
+ }
+ catch (SQLException e) {
+ assertSame("Connection is closed", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testUseAfterClose() throws SQLException
+ {
+ try (Connection connection = ds.getConnection()) {
+ assertNotNull(connection);
+ connection.close();
+
+ try (Statement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?")) {
+ fail();
+ }
+ catch (SQLException e) {
+ assertSame("Connection is closed", e.getMessage());
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java b/src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java
new file mode 100644
index 0000000..1a7caa7
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static org.junit.Assert.assertEquals;
+
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.util.UtilityElf;
+
+/**
+ * @author Martin Stříž (striz@raynet.cz)
+ */
+public class HouseKeeperCleanupTest
+{
+
+ private ScheduledThreadPoolExecutor executor;
+
+ @Before
+ public void before() throws Exception
+ {
+ ThreadFactory threadFactory = new UtilityElf.DefaultThreadFactory("global housekeeper", true);
+
+ executor = new ScheduledThreadPoolExecutor(1, threadFactory, new ThreadPoolExecutor.DiscardPolicy());
+ executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
+ executor.setRemoveOnCancelPolicy(true);
+ }
+
+ @Test
+ public void testHouseKeeperCleanupWithCustomExecutor() throws Exception
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(10);
+ config.setInitializationFailTimeout(Long.MAX_VALUE);
+ config.setConnectionTimeout(2500);
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+ config.setScheduledExecutorService(executor);
+
+ HikariConfig config2 = newHikariConfig();
+ config.copyState(config2);
+
+ try (
+ final HikariDataSource ds1 = new HikariDataSource(config);
+ final HikariDataSource ds2 = new HikariDataSource(config2)
+ ) {
+ assertEquals("Scheduled tasks count not as expected, ", 2, executor.getQueue().size());
+ }
+
+ assertEquals("Scheduled tasks count not as expected, ", 0, executor.getQueue().size());
+ }
+
+ @After
+ public void after() throws Exception
+ {
+ executor.shutdown();
+ executor.awaitTermination(5, TimeUnit.SECONDS);
+ }
+
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/IsolationTest.java b/src/test/java/com/zaxxer/hikari/pool/IsolationTest.java
new file mode 100644
index 0000000..01a80ae
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/IsolationTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariDataSource;
+
+public class IsolationTest
+{
+ @Test
+ public void testIsolation() throws SQLException
+ {
+ try (HikariDataSource ds = newHikariDataSource()) {
+ ds.setMinimumIdle(1);
+ ds.setMaximumPoolSize(1);
+ ds.setIsolateInternalQueries(true);
+ ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (Connection connection = ds.getConnection()) {
+ connection.close();
+
+ try (Connection connection2 = ds.getConnection()) {
+ connection2.close();
+
+ assertNotSame(connection, connection2);
+ assertSame(connection.unwrap(Connection.class), connection2.unwrap(Connection.class));
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testNonIsolation() throws SQLException
+ {
+ try (HikariDataSource ds = newHikariDataSource()) {
+ ds.setMinimumIdle(1);
+ ds.setMaximumPoolSize(1);
+ ds.setIsolateInternalQueries(false);
+ ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (Connection connection = ds.getConnection()) {
+ connection.close();
+
+ try (Connection connection2 = ds.getConnection()) {
+ connection2.close();
+
+ assertSame(connection.unwrap(Connection.class), connection2.unwrap(Connection.class));
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/JdbcDriverTest.java b/src/test/java/com/zaxxer/hikari/pool/JdbcDriverTest.java
new file mode 100644
index 0000000..0d9cc2f
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/JdbcDriverTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import org.junit.After;
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.util.DriverDataSource;
+
+public class JdbcDriverTest
+{
+ private HikariDataSource ds;
+
+ @After
+ public void teardown()
+ {
+ if (ds != null) {
+ ds.close();
+ }
+ }
+
+ @Test
+ public void driverTest1() throws SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(1);
+ config.setMaximumPoolSize(1);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDriverClassName("com.zaxxer.hikari.mocks.StubDriver");
+ config.setJdbcUrl("jdbc:stub");
+ config.addDataSourceProperty("user", "bart");
+ config.addDataSourceProperty("password", "simpson");
+
+ ds = new HikariDataSource(config);
+
+ assertTrue(ds.isWrapperFor(DriverDataSource.class));
+
+ DriverDataSource unwrap = ds.unwrap(DriverDataSource.class);
+ assertNotNull(unwrap);
+
+ try (Connection connection = ds.getConnection()) {
+ // test that getConnection() succeeds
+ }
+ }
+
+ @Test
+ public void driverTest2() throws SQLException
+ {
+ HikariConfig config = newHikariConfig();
+
+ config.setMinimumIdle(1);
+ config.setMaximumPoolSize(1);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDriverClassName("com.zaxxer.hikari.mocks.StubDriver");
+ config.setJdbcUrl("jdbc:invalid");
+
+ try {
+ ds = new HikariDataSource(config);
+ }
+ catch (RuntimeException e) {
+ assertTrue(e.getMessage().contains("claims to not accept"));
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/MiscTest.java b/src/test/java/com/zaxxer/hikari/pool/MiscTest.java
new file mode 100644
index 0000000..f9698ef
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/MiscTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.getPool;
+import static com.zaxxer.hikari.pool.TestElf.setConfigUnitTest;
+import static com.zaxxer.hikari.pool.TestElf.setSlf4jLogLevel;
+import static com.zaxxer.hikari.pool.TestElf.setSlf4jTargetStream;
+import static com.zaxxer.hikari.util.UtilityElf.createInstance;
+import static com.zaxxer.hikari.util.UtilityElf.getTransactionIsolation;
+import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.logging.log4j.Level;
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+/**
+ * @author Brett Wooldridge
+ */
+public class MiscTest
+{
+ @Test
+ public void testLogWriter() throws SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(4);
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+ setConfigUnitTest(true);
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ PrintWriter writer = new PrintWriter(System.out);
+ ds.setLogWriter(writer);
+ assertSame(writer, ds.getLogWriter());
+ assertEquals("testLogWriter", config.getPoolName());
+ }
+ finally
+ {
+ setConfigUnitTest(false);
+ }
+ }
+
+ @Test
+ public void testInvalidIsolation()
+ {
+ try {
+ getTransactionIsolation("INVALID");
+ fail();
+ }
+ catch (Exception e) {
+ assertTrue(e instanceof IllegalArgumentException);
+ }
+ }
+
+ @Test
+ public void testCreateInstance()
+ {
+ try {
+ createInstance("invalid", null);
+ fail();
+ }
+ catch (RuntimeException e) {
+ assertTrue(e.getCause() instanceof ClassNotFoundException);
+ }
+ }
+
+ @Test
+ public void testLeakDetection() throws Exception
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (PrintStream ps = new PrintStream(baos, true)) {
+ setSlf4jTargetStream(Class.forName("com.zaxxer.hikari.pool.ProxyLeakTask"), ps);
+ setConfigUnitTest(true);
+
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(4);
+ config.setThreadFactory(Executors.defaultThreadFactory());
+ config.setMetricRegistry(null);
+ config.setLeakDetectionThreshold(TimeUnit.SECONDS.toMillis(1));
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ setSlf4jLogLevel(HikariPool.class, Level.DEBUG);
+ getPool(ds).logPoolState();
+
+ try (Connection connection = ds.getConnection()) {
+ quietlySleep(SECONDS.toMillis(4));
+ connection.close();
+ quietlySleep(SECONDS.toMillis(1));
+ ps.close();
+ String s = new String(baos.toByteArray());
+ assertNotNull("Exception string was null", s);
+ assertTrue("Expected exception to contain 'Connection leak detection' but contains *" + s + "*", s.contains("Connection leak detection"));
+ }
+ }
+ finally
+ {
+ setConfigUnitTest(false);
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/PostgresTest.java b/src/test/java/com/zaxxer/hikari/pool/PostgresTest.java
new file mode 100644
index 0000000..693e7b3
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/PostgresTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.getPool;
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.util.ClockSource.currentTime;
+import static com.zaxxer.hikari.util.ClockSource.elapsedMillis;
+import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.util.UtilityElf;
+
+/**
+ * This test is meant to be run manually and interactively and was
+ * build for issue #159.
+ *
+ * @author Brett Wooldridge
+ */
+public class PostgresTest
+{
+ //@Test
+ public void testCase1() throws Exception
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(3);
+ config.setMaximumPoolSize(10);
+ config.setConnectionTimeout(3000);
+ config.setIdleTimeout(TimeUnit.SECONDS.toMillis(10));
+ config.setValidationTimeout(TimeUnit.SECONDS.toMillis(2));
+
+ config.setJdbcUrl("jdbc:pgsql://localhost:5432/test");
+ config.setUsername("brettw");
+
+ try (final HikariDataSource ds = new HikariDataSource(config)) {
+ final long start = currentTime();
+ do {
+ Thread t = new Thread() {
+ public void run() {
+ try (Connection connection = ds.getConnection()) {
+ System.err.println("Obtained connection " + connection);
+ quietlySleep(TimeUnit.SECONDS.toMillis((long)(10 + (Math.random() * 20))));
+ }
+ catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+ };
+ t.setDaemon(true);
+ t.start();
+
+ quietlySleep(TimeUnit.SECONDS.toMillis((long)((Math.random() * 20))));
+ } while (elapsedMillis(start) < MINUTES.toMillis(15));
+ }
+ }
+
+ //@Test
+ public void testCase2() throws Exception
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(3);
+ config.setMaximumPoolSize(10);
+ config.setConnectionTimeout(1000);
+ config.setIdleTimeout(TimeUnit.SECONDS.toMillis(60));
+
+ config.setJdbcUrl("jdbc:pgsql://localhost:5432/test");
+ config.setUsername("brettw");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+
+ try (Connection conn = ds.getConnection()) {
+ System.err.println("\nGot a connection, and released it. Now, enable the firewall.");
+ }
+
+ getPool(ds).logPoolState();
+ quietlySleep(5000L);
+
+ System.err.println("\nNow attempting another getConnection(), expecting a timeout...");
+
+ long start = currentTime();
+ try (Connection conn = ds.getConnection()) {
+ System.err.println("\nOpps, got a connection. Did you enable the firewall? " + conn);
+ fail("Opps, got a connection. Did you enable the firewall?");
+ }
+ catch (SQLException e)
+ {
+ assertTrue("Timeout less than expected " + elapsedMillis(start) + "ms", elapsedMillis(start) > 5000);
+ }
+
+ System.err.println("\nOk, so far so good. Now, disable the firewall again. Attempting connection in 5 seconds...");
+ quietlySleep(5000L);
+ getPool(ds).logPoolState();
+
+ try (Connection conn = ds.getConnection()) {
+ System.err.println("\nGot a connection, and released it.");
+ }
+ }
+
+ System.err.println("\nPassed.");
+ }
+
+ //@Test
+ public void testCase3() throws Exception
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(3);
+ config.setMaximumPoolSize(10);
+ config.setConnectionTimeout(1000);
+ config.setIdleTimeout(TimeUnit.SECONDS.toMillis(60));
+
+ config.setJdbcUrl("jdbc:pgsql://localhost:5432/test");
+ config.setUsername("brettw");
+
+ try (final HikariDataSource ds = new HikariDataSource(config)) {
+ for (int i = 0; i < 10; i++) {
+ new Thread() {
+ public void run() {
+ try (Connection conn = ds.getConnection()) {
+ System.err.println("ERROR: should not have acquired connection.");
+ }
+ catch (SQLException e) {
+ // expected
+ }
+ }
+ }.start();
+ }
+
+ quietlySleep(5000L);
+
+ System.err.println("Now, bring the DB online. Checking pool in 15 seconds.");
+ quietlySleep(15000L);
+
+ getPool(ds).logPoolState();
+ }
+ }
+
+ // @Test
+ public void testCase4() throws Exception
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(15);
+ config.setConnectionTimeout(10000);
+ config.setIdleTimeout(TimeUnit.MINUTES.toMillis(1));
+ config.setMaxLifetime(TimeUnit.MINUTES.toMillis(2));
+ config.setRegisterMbeans(true);
+
+ config.setJdbcUrl("jdbc:postgresql://localhost:5432/netld");
+ config.setUsername("brettw");
+
+ try (final HikariDataSource ds = new HikariDataSource(config)) {
+
+ countdown(20);
+ List<Thread> threads = new ArrayList<>();
+ for (int i = 0; i < 20; i++) {
+ threads.add(new Thread() {
+ public void run() {
+ UtilityElf.quietlySleep((long)(Math.random() * 2500L));
+ final long start = currentTime();
+ do {
+ try (Connection conn = ds.getConnection(); Statement stmt = conn.createStatement()) {
+ try (ResultSet rs = stmt.executeQuery("SELECT * FROM device WHERE device_id=0 ORDER BY device_id LIMIT 1 OFFSET 0")) {
+ rs.next();
+ }
+ UtilityElf.quietlySleep(100L); //Math.max(50L, (long)(Math.random() * 250L)));
+ }
+ catch (SQLException e) {
+ e.printStackTrace();
+ // throw new RuntimeException(e);
+ }
+
+ // UtilityElf.quietlySleep(10L); //Math.max(50L, (long)(Math.random() * 250L)));
+ } while (elapsedMillis(start) < TimeUnit.MINUTES.toMillis(5));
+ }
+ });
+ }
+
+// threads.forEach(t -> t.start());
+// threads.forEach(t -> { try { t.join(); } catch (InterruptedException e) {} });
+ }
+ }
+
+ @Before
+ public void before()
+ {
+ System.err.println("\n");
+ }
+
+ private void countdown(int seconds)
+ {
+ do {
+ System.out.printf("Starting in %d seconds...\n", seconds);
+ if (seconds > 10) {
+ UtilityElf.quietlySleep(TimeUnit.SECONDS.toMillis(10));
+ seconds -= 10;
+ }
+ else {
+ UtilityElf.quietlySleep(TimeUnit.SECONDS.toMillis(1));
+ seconds -= 1;
+ }
+ } while (seconds > 0);
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/RampUpDown.java b/src/test/java/com/zaxxer/hikari/pool/RampUpDown.java
new file mode 100755
index 0000000..e09894d
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/RampUpDown.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.getPool;
+import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
+import static org.junit.Assert.assertSame;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+public class RampUpDown
+{
+ @Test
+ public void rampUpDownTest() throws SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(5);
+ config.setMaximumPoolSize(60);
+ config.setInitializationFailTimeout(0);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "250");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+
+ ds.setIdleTimeout(1000);
+ HikariPool pool = getPool(ds);
+
+ // wait two housekeeping periods so we don't fail if this part of test runs too quickly
+ quietlySleep(500);
+
+ Assert.assertSame("Total connections not as expected", 5, pool.getTotalConnections());
+
+ Connection[] connections = new Connection[ds.getMaximumPoolSize()];
+ for (int i = 0; i < connections.length; i++)
+ {
+ connections[i] = ds.getConnection();
+ }
+
+ assertSame("Total connections not as expected", 60, pool.getTotalConnections());
+
+ for (Connection connection : connections)
+ {
+ connection.close();
+ }
+
+ quietlySleep(500);
+
+ assertSame("Total connections not as expected", 5, pool.getTotalConnections());
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/ShutdownTest.java b/src/test/java/com/zaxxer/hikari/pool/ShutdownTest.java
new file mode 100644
index 0000000..aa12a43
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/ShutdownTest.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.getPool;
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.setSlf4jLogLevel;
+import static com.zaxxer.hikari.util.ClockSource.currentTime;
+import static com.zaxxer.hikari.util.ClockSource.elapsedMillis;
+import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+import org.apache.logging.log4j.Level;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.mocks.StubConnection;
+import com.zaxxer.hikari.util.UtilityElf;
+
+/**
+ * @author Brett Wooldridge
+ */
+public class ShutdownTest
+{
+ @Before
+ public void beforeTest()
+ {
+ setSlf4jLogLevel(PoolBase.class, Level.DEBUG);
+ setSlf4jLogLevel(HikariPool.class, Level.DEBUG);
+ StubConnection.count.set(0);
+ }
+
+ @After
+ public void afterTest()
+ {
+ setSlf4jLogLevel(PoolBase.class, Level.WARN);
+ setSlf4jLogLevel(HikariPool.class, Level.WARN);
+ StubConnection.slowCreate = false;
+ }
+
+ @Test
+ public void testShutdown1() throws SQLException
+ {
+ Assert.assertSame("StubConnection count not as expected", 0, StubConnection.count.get());
+
+ StubConnection.slowCreate = true;
+
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(10);
+ config.setInitializationFailTimeout(Long.MAX_VALUE);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ HikariPool pool = getPool(ds);
+
+ Thread[] threads = new Thread[10];
+ for (int i = 0; i < 10; i++) {
+ threads[i] = new Thread() {
+ @Override
+ public void run()
+ {
+ try {
+ if (ds.getConnection() != null) {
+ quietlySleep(SECONDS.toMillis(1));
+ }
+ }
+ catch (SQLException e) {
+ }
+ }
+ };
+ threads[i].setDaemon(true);
+ }
+ for (int i = 0; i < 10; i++) {
+ threads[i].start();
+ }
+
+ quietlySleep(1800L);
+
+ assertTrue("Total connection count not as expected, ", pool.getTotalConnections() > 0);
+
+ ds.close();
+
+ assertSame("Active connection count not as expected, ", 0, pool.getActiveConnections());
+ assertSame("Idle connection count not as expected, ", 0, pool.getIdleConnections());
+ assertSame("Total connection count not as expected, ", 0, pool.getTotalConnections());
+ assertTrue(ds.isClosed());
+ }
+ }
+
+ @Test
+ public void testShutdown2() throws SQLException
+ {
+ assertSame("StubConnection count not as expected", 0, StubConnection.count.get());
+
+ StubConnection.slowCreate = true;
+
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(10);
+ config.setMaximumPoolSize(10);
+ config.setInitializationFailTimeout(0);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ HikariPool pool = getPool(ds);
+
+ quietlySleep(1200L);
+
+ assertTrue("Total connection count not as expected, ", pool.getTotalConnections() > 0);
+
+ ds.close();
+
+ assertSame("Active connection count not as expected, ", 0, pool.getActiveConnections());
+ assertSame("Idle connection count not as expected, ", 0, pool.getIdleConnections());
+ assertSame("Total connection count not as expected, ", 0, pool.getTotalConnections());
+ assertTrue(ds.toString().startsWith("HikariDataSource (") && ds.toString().endsWith(")"));
+ }
+ }
+
+ @Test
+ public void testShutdown3() throws SQLException
+ {
+ assertSame("StubConnection count not as expected", 0, StubConnection.count.get());
+
+ StubConnection.slowCreate = false;
+
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(5);
+ config.setMaximumPoolSize(5);
+ config.setInitializationFailTimeout(0);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ HikariPool pool = getPool(ds);
+
+ quietlySleep(1200L);
+
+ assertTrue("Total connection count not as expected, ", pool.getTotalConnections() == 5);
+
+ ds.close();
+
+ assertSame("Active connection count not as expected, ", 0, pool.getActiveConnections());
+ assertSame("Idle connection count not as expected, ", 0, pool.getIdleConnections());
+ assertSame("Total connection count not as expected, ", 0, pool.getTotalConnections());
+ }
+ }
+
+ @Test
+ public void testShutdown4() throws SQLException
+ {
+ StubConnection.slowCreate = true;
+
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(10);
+ config.setMaximumPoolSize(10);
+ config.setInitializationFailTimeout(Long.MAX_VALUE);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ quietlySleep(500L);
+
+ ds.close();
+
+ long startTime = currentTime();
+ while (elapsedMillis(startTime) < SECONDS.toMillis(5) && threadCount() > 0) {
+ quietlySleep(250);
+ }
+
+ assertSame("Unreleased connections after shutdown", 0, getPool(ds).getTotalConnections());
+ }
+ }
+
+ @Test
+ public void testShutdown5() throws SQLException
+ {
+ Assert.assertSame("StubConnection count not as expected", 0, StubConnection.count.get());
+
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(5);
+ config.setMaximumPoolSize(5);
+ config.setInitializationFailTimeout(0);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ HikariPool pool = getPool(ds);
+
+ Connection[] connections = new Connection[5];
+ for (int i = 0; i < 5; i++) {
+ connections[i] = ds.getConnection();
+ }
+
+ Assert.assertTrue("Total connection count not as expected, ", pool.getTotalConnections() == 5);
+
+ ds.close();
+
+ Assert.assertSame("Active connection count not as expected, ", 0, pool.getActiveConnections());
+ Assert.assertSame("Idle connection count not as expected, ", 0, pool.getIdleConnections());
+ Assert.assertSame("Total connection count not as expected, ", 0, pool.getTotalConnections());
+ }
+ }
+
+ @Test
+ public void testAfterShutdown() throws Exception
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(5);
+ config.setInitializationFailTimeout(0);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ ds.close();
+ try {
+ ds.getConnection();
+ }
+ catch (SQLException e) {
+ Assert.assertTrue(e.getMessage().contains("has been closed."));
+ }
+ }
+ }
+
+ @Test
+ public void testShutdownDuringInit() throws Exception
+ {
+ final HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(5);
+ config.setMaximumPoolSize(5);
+ config.setConnectionTimeout(1000);
+ config.setValidationTimeout(1000);
+ config.setInitializationFailTimeout(0);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ StubConnection.slowCreate = true;
+ UtilityElf.quietlySleep(3000L);
+ }
+ }
+
+ @Test
+ public void testThreadedShutdown() throws Exception
+ {
+ final HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(5);
+ config.setMaximumPoolSize(5);
+ config.setConnectionTimeout(1000);
+ config.setValidationTimeout(1000);
+ config.setInitializationFailTimeout(0);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ for (int i = 0; i < 4; i++) {
+ try (final HikariDataSource ds = new HikariDataSource(config)) {
+ Thread t = new Thread() {
+ @Override
+ public void run()
+ {
+ try (Connection connection = ds.getConnection()) {
+ for (int i = 0; i < 10; i++) {
+ Connection connection2 = null;
+ try {
+ connection2 = ds.getConnection();
+ PreparedStatement stmt = connection2.prepareStatement("SOMETHING");
+ UtilityElf.quietlySleep(20);
+ stmt.getMaxFieldSize();
+ }
+ catch (SQLException e) {
+ try {
+ if (connection2 != null) {
+ connection2.close();
+ }
+ }
+ catch (SQLException e2) {
+ if (e2.getMessage().contains("shutdown") || e2.getMessage().contains("evicted")) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ catch (Exception e) {
+ Assert.fail(e.getMessage());
+ }
+ finally {
+ ds.close();
+ }
+ }
+ };
+ t.start();
+
+ Thread t2 = new Thread() {
+ @Override
+ public void run()
+ {
+ UtilityElf.quietlySleep(100);
+ try {
+ ds.close();
+ }
+ catch (IllegalStateException e) {
+ Assert.fail(e.getMessage());
+ }
+ }
+ };
+ t2.start();
+
+ t.join();
+ t2.join();
+
+ ds.close();
+ }
+ }
+ }
+
+ private int threadCount()
+ {
+ Thread[] threads = new Thread[Thread.activeCount() * 2];
+ Thread.enumerate(threads);
+
+ int count = 0;
+ for (Thread thread : threads) {
+ count += (thread != null && thread.getName().startsWith("Hikari")) ? 1 : 0;
+ }
+
+ return count;
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/StatementTest.java b/src/test/java/com/zaxxer/hikari/pool/StatementTest.java
new file mode 100644
index 0000000..ebe9c90
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/StatementTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.getPool;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+public class StatementTest
+{
+ private HikariDataSource ds;
+
+ @Before
+ public void setup()
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(1);
+ config.setMaximumPoolSize(2);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ ds = new HikariDataSource(config);
+ }
+
+ @After
+ public void teardown()
+ {
+ ds.close();
+ }
+
+ @Test
+ public void testStatementClose() throws SQLException
+ {
+ ds.getConnection().close();
+
+ HikariPool pool = getPool(ds);
+ assertTrue("Total connections not as expected", pool.getTotalConnections() >= 1);
+ assertTrue("Idle connections not as expected", pool.getIdleConnections() >= 1);
+
+ try (Connection connection = ds.getConnection()) {
+ assertNotNull(connection);
+
+ assertTrue("Total connections not as expected", pool.getTotalConnections() >= 1);
+ assertTrue("Idle connections not as expected", pool.getIdleConnections() >= 0);
+
+ Statement statement = connection.createStatement();
+ assertNotNull(statement);
+
+ connection.close();
+
+ assertTrue(statement.isClosed());
+ }
+ }
+
+ @Test
+ public void testAutoStatementClose() throws SQLException
+ {
+ try (Connection connection = ds.getConnection()) {
+ assertNotNull(connection);
+
+ Statement statement1 = connection.createStatement();
+ assertNotNull(statement1);
+ Statement statement2 = connection.createStatement();
+ assertNotNull(statement2);
+
+ connection.close();
+
+ assertTrue(statement1.isClosed());
+ assertTrue(statement2.isClosed());
+ }
+ }
+
+ @Test
+ public void testDoubleStatementClose() throws SQLException
+ {
+ try (Connection connection = ds.getConnection();
+ Statement statement1 = connection.createStatement()) {
+ statement1.close();
+ statement1.close();
+ }
+ }
+
+ @Test
+ public void testOutOfOrderStatementClose() throws SQLException
+ {
+ try (Connection connection = ds.getConnection();
+ Statement statement1 = connection.createStatement();
+ Statement statement2 = connection.createStatement()) {
+ statement1.close();
+ statement2.close();
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestConcurrentBag.java b/src/test/java/com/zaxxer/hikari/pool/TestConcurrentBag.java
new file mode 100644
index 0000000..b8d9d22
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/TestConcurrentBag.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.getPool;
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.setSlf4jTargetStream;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.concurrent.CompletableFuture;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.util.ConcurrentBag;
+
+/**
+ *
+ * @author Brett Wooldridge
+ */
+public class TestConcurrentBag
+{
+ private static HikariDataSource ds;
+ private static HikariPool pool;
+
+ @BeforeClass
+ public static void setup()
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(1);
+ config.setMaximumPoolSize(2);
+ config.setInitializationFailTimeout(0);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ ds = new HikariDataSource(config);
+ pool = getPool(ds);
+ }
+
+ @AfterClass
+ public static void teardown()
+ {
+ ds.close();
+ }
+
+ @Test
+ public void testConcurrentBag() throws Exception
+ {
+ try (ConcurrentBag<PoolEntry> bag = new ConcurrentBag<>((x) -> CompletableFuture.completedFuture(Boolean.TRUE))) {
+ assertEquals(0, bag.values(8).size());
+
+ PoolEntry reserved = pool.newPoolEntry();
+ bag.add(reserved);
+ bag.reserve(reserved); // reserved
+
+ PoolEntry inuse = pool.newPoolEntry();
+ bag.add(inuse);
+ bag.borrow(2, MILLISECONDS); // in use
+
+ PoolEntry notinuse = pool.newPoolEntry();
+ bag.add(notinuse); // not in use
+
+ bag.dumpState();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(baos, true);
+ setSlf4jTargetStream(ConcurrentBag.class, ps);
+
+ bag.requite(reserved);
+
+ bag.remove(notinuse);
+ assertTrue(new String(baos.toByteArray()).contains("not borrowed or reserved"));
+
+ bag.unreserve(notinuse);
+ assertTrue(new String(baos.toByteArray()).contains("was not reserved"));
+
+ bag.remove(inuse);
+ bag.remove(inuse);
+ assertTrue(new String(baos.toByteArray()).contains("not borrowed or reserved"));
+
+ bag.close();
+ try {
+ PoolEntry bagEntry = pool.newPoolEntry();
+ bag.add(bagEntry);
+ assertNotEquals(bagEntry, bag.borrow(100, MILLISECONDS));
+ }
+ catch (IllegalStateException e) {
+ assertTrue(new String(baos.toByteArray()).contains("ignoring add()"));
+ }
+
+ assertNotNull(notinuse.toString());
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestConnectionCloseBlocking.java b/src/test/java/com/zaxxer/hikari/pool/TestConnectionCloseBlocking.java
new file mode 100644
index 0000000..8d98f83
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/TestConnectionCloseBlocking.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+/**
+ *
+ */
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.util.ClockSource.currentTime;
+import static com.zaxxer.hikari.util.ClockSource.elapsedMillis;
+import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.mocks.MockDataSource;
+
+/**
+ * Test for cases when db network connectivity goes down and close is called on existing connections. By default Hikari
+ * blocks longer than getMaximumTimeout (it can hang for a lot of time depending on driver timeout settings). Closing
+ * async the connections fixes this issue.
+ *
+ */
+public class TestConnectionCloseBlocking {
+ private static volatile boolean shouldFail = false;
+
+ // @Test
+ public void testConnectionCloseBlocking() throws SQLException {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(1);
+ config.setConnectionTimeout(1500);
+ config.setDataSource(new CustomMockDataSource());
+
+ long start = currentTime();
+ try (HikariDataSource ds = new HikariDataSource(config);
+ Connection connection = ds.getConnection()) {
+
+ connection.close();
+
+ // Hikari only checks for validity for connections with lastAccess > 1000 ms so we sleep for 1001 ms to force
+ // Hikari to do a connection validation which will fail and will trigger the connection to be closed
+ quietlySleep(1100L);
+
+ shouldFail = true;
+
+ // on physical connection close we sleep 2 seconds
+ try (Connection connection2 = ds.getConnection()) {
+ assertTrue("Waited longer than timeout", (elapsedMillis(start) < config.getConnectionTimeout()));
+ }
+ } catch (SQLException e) {
+ assertTrue("getConnection failed because close connection took longer than timeout", (elapsedMillis(start) < config.getConnectionTimeout()));
+ }
+ }
+
+ private static class CustomMockDataSource extends MockDataSource {
+ @Override
+ public Connection getConnection() throws SQLException {
+ Connection mockConnection = super.getConnection();
+ when(mockConnection.isValid(anyInt())).thenReturn(!shouldFail);
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ if (shouldFail) {
+ SECONDS.sleep(2);
+ }
+ return null;
+ }
+ }).when(mockConnection).close();
+ return mockConnection;
+ }
+ }
+
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java b/src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java
new file mode 100644
index 0000000..15f8974
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.getPool;
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.setSlf4jLogLevel;
+import static com.zaxxer.hikari.pool.TestElf.setSlf4jTargetStream;
+import static com.zaxxer.hikari.util.ClockSource.currentTime;
+import static com.zaxxer.hikari.util.ClockSource.elapsedMillis;
+import static java.lang.Thread.sleep;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.logging.log4j.Level;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.mocks.StubConnection;
+import com.zaxxer.hikari.mocks.StubDataSource;
+
+public class TestConnectionTimeoutRetry
+{
+ @Test
+ public void testConnectionRetries() throws SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(1);
+ config.setConnectionTimeout(2800);
+ config.setValidationTimeout(2800);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ StubDataSource stubDataSource = ds.unwrap(StubDataSource.class);
+ stubDataSource.setThrowException(new SQLException("Connection refused"));
+
+ long start = currentTime();
+ try (Connection connection = ds.getConnection()) {
+ connection.close();
+ fail("Should not have been able to get a connection.");
+ }
+ catch (SQLException e) {
+ long elapsed = elapsedMillis(start);
+ long timeout = config.getConnectionTimeout();
+ assertTrue("Didn't wait long enough for timeout", (elapsed >= timeout));
+ }
+ }
+ }
+
+ @Test
+ public void testConnectionRetries2() throws SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(1);
+ config.setConnectionTimeout(2800);
+ config.setValidationTimeout(2800);
+ config.setInitializationFailTimeout(0);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ final StubDataSource stubDataSource = ds.unwrap(StubDataSource.class);
+ stubDataSource.setThrowException(new SQLException("Connection refused"));
+
+ ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
+ scheduler.schedule(new Runnable() {
+ @Override
+ public void run()
+ {
+ stubDataSource.setThrowException(null);
+ }
+ }, 300, TimeUnit.MILLISECONDS);
+
+ long start = currentTime();
+ try {
+ try (Connection connection = ds.getConnection()) {
+ // close immediately
+ }
+
+ long elapsed = elapsedMillis(start);
+ assertTrue("Connection returned too quickly, something is wrong.", elapsed > 250);
+ assertTrue("Waited too long to get a connection.", elapsed < config.getConnectionTimeout());
+ }
+ catch (SQLException e) {
+ fail("Should not have timed out: " + e.getMessage());
+ }
+ finally {
+ scheduler.shutdownNow();
+ }
+ }
+ }
+
+ @Test
+ public void testConnectionRetries3() throws SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(2);
+ config.setConnectionTimeout(2800);
+ config.setValidationTimeout(2800);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ final Connection connection1 = ds.getConnection();
+ final Connection connection2 = ds.getConnection();
+ assertNotNull(connection1);
+ assertNotNull(connection2);
+
+ ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
+ scheduler.schedule(new Runnable() {
+ @Override
+ public void run()
+ {
+ try {
+ connection1.close();
+ }
+ catch (Exception e) {
+ e.printStackTrace(System.err);
+ }
+ }
+ }, 800, MILLISECONDS);
+
+ long start = currentTime();
+ try {
+ try (Connection connection3 = ds.getConnection()) {
+ // close immediately
+ }
+
+ long elapsed = elapsedMillis(start);
+ assertTrue("Waited too long to get a connection.", (elapsed >= 700) && (elapsed < 950));
+ }
+ catch (SQLException e) {
+ fail("Should not have timed out.");
+ }
+ finally {
+ scheduler.shutdownNow();
+ }
+ }
+ }
+
+ @Test
+ public void testConnectionRetries5() throws SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(2);
+ config.setConnectionTimeout(1000);
+ config.setValidationTimeout(1000);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ final Connection connection1 = ds.getConnection();
+
+ long start = currentTime();
+
+ ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
+ scheduler.schedule(new Runnable() {
+ @Override
+ public void run()
+ {
+ try {
+ connection1.close();
+ }
+ catch (Exception e) {
+ e.printStackTrace(System.err);
+ }
+ }
+ }, 250, MILLISECONDS);
+
+ StubDataSource stubDataSource = ds.unwrap(StubDataSource.class);
+ stubDataSource.setThrowException(new SQLException("Connection refused"));
+
+ try {
+ try (Connection connection2 = ds.getConnection()) {
+ // close immediately
+ }
+
+ long elapsed = elapsedMillis(start);
+ assertTrue("Waited too long to get a connection.", (elapsed >= 250) && (elapsed < config.getConnectionTimeout()));
+ }
+ catch (SQLException e) {
+ fail("Should not have timed out.");
+ }
+ finally {
+ scheduler.shutdownNow();
+ }
+ }
+ }
+
+ @Test
+ public void testConnectionIdleFill() throws Exception
+ {
+ StubConnection.slowCreate = false;
+
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(5);
+ config.setMaximumPoolSize(10);
+ config.setConnectionTimeout(2000);
+ config.setValidationTimeout(2000);
+ config.setConnectionTestQuery("VALUES 2");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "400");
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(baos, true);
+ setSlf4jTargetStream(HikariPool.class, ps);
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ setSlf4jLogLevel(HikariPool.class, Level.DEBUG);
+
+ HikariPool pool = getPool(ds);
+ try (
+ Connection connection1 = ds.getConnection();
+ Connection connection2 = ds.getConnection();
+ Connection connection3 = ds.getConnection();
+ Connection connection4 = ds.getConnection();
+ Connection connection5 = ds.getConnection();
+ Connection connection6 = ds.getConnection();
+ Connection connection7 = ds.getConnection()) {
+
+ sleep(1300);
+
+ assertSame("Total connections not as expected", 10, pool.getTotalConnections());
+ assertSame("Idle connections not as expected", 3, pool.getIdleConnections());
+ }
+
+ assertSame("Total connections not as expected", 10, pool.getTotalConnections());
+ assertSame("Idle connections not as expected", 10, pool.getIdleConnections());
+ }
+ }
+
+ @Before
+ public void before()
+ {
+ setSlf4jLogLevel(HikariPool.class, Level.INFO);
+ }
+
+ @After
+ public void after()
+ {
+ System.getProperties().remove("com.zaxxer.hikari.housekeeping.periodMs");
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestConnections.java b/src/test/java/com/zaxxer/hikari/pool/TestConnections.java
new file mode 100644
index 0000000..538d9ac
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/TestConnections.java
@@ -0,0 +1,621 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource;
+import static com.zaxxer.hikari.pool.TestElf.getPool;
+import static com.zaxxer.hikari.pool.TestElf.setConfigUnitTest;
+import static com.zaxxer.hikari.pool.TestElf.setSlf4jLogLevel;
+import static com.zaxxer.hikari.pool.TestElf.setSlf4jTargetStream;
+import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLTransientConnectionException;
+import java.sql.Statement;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.logging.log4j.Level;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.mocks.StubConnection;
+import com.zaxxer.hikari.mocks.StubDataSource;
+import com.zaxxer.hikari.mocks.StubStatement;
+import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException;
+
+/**
+ * @author Brett Wooldridge
+ */
+public class TestConnections
+{
+ @Before
+ public void before()
+ {
+ setSlf4jTargetStream(HikariPool.class, System.err);
+ setSlf4jLogLevel(HikariPool.class, Level.DEBUG);
+ setSlf4jLogLevel(PoolBase.class, Level.DEBUG);
+ }
+
+ @After
+ public void after()
+ {
+ System.getProperties().remove("com.zaxxer.hikari.housekeeping.periodMs");
+ setSlf4jLogLevel(HikariPool.class, Level.WARN);
+ setSlf4jLogLevel(PoolBase.class, Level.WARN);
+ }
+
+ @Test
+ public void testCreate() throws SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(1);
+ config.setMaximumPoolSize(1);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setConnectionInitSql("SELECT 1");
+ config.setReadOnly(true);
+ config.setConnectionTimeout(2500);
+ config.setLeakDetectionThreshold(TimeUnit.SECONDS.toMillis(30));
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ ds.setLoginTimeout(10);
+ assertSame(10, ds.getLoginTimeout());
+
+ HikariPool pool = getPool(ds);
+ ds.getConnection().close();
+ assertSame("Total connections not as expected", 1, pool.getTotalConnections());
+ assertSame("Idle connections not as expected", 1, pool.getIdleConnections());
+
+ try (Connection connection = ds.getConnection();
+ PreparedStatement statement = connection.prepareStatement("SELECT * FROM device WHERE device_id=?")) {
+
+ assertNotNull(connection);
+ assertNotNull(statement);
+
+ assertSame("Total connections not as expected", 1, pool.getTotalConnections());
+ assertSame("Idle connections not as expected", 0, pool.getIdleConnections());
+
+ statement.setInt(1, 0);
+
+ try (ResultSet resultSet = statement.executeQuery()) {
+ assertNotNull(resultSet);
+
+ assertFalse(resultSet.next());
+ }
+ }
+
+ assertSame("Total connections not as expected", 1, pool.getTotalConnections());
+ assertSame("Idle connections not as expected", 1, pool.getIdleConnections());
+ }
+ }
+
+ @Test
+ public void testMaxLifetime() throws Exception
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(1);
+ config.setConnectionTimeout(2500);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setInitializationFailTimeout(Long.MAX_VALUE);
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "100");
+
+ setConfigUnitTest(true);
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs");
+
+ ds.setMaxLifetime(700);
+
+ HikariPool pool = getPool(ds);
+
+ assertSame("Total connections not as expected", 0, pool.getTotalConnections());
+ assertSame("Idle connections not as expected", 0, pool.getIdleConnections());
+
+ Connection unwrap;
+ Connection unwrap2;
+ try (Connection connection = ds.getConnection()) {
+ unwrap = connection.unwrap(Connection.class);
+ assertNotNull(connection);
+
+ assertSame("Second total connections not as expected", 1, pool.getTotalConnections());
+ assertSame("Second idle connections not as expected", 0, pool.getIdleConnections());
+ }
+
+ assertSame("Idle connections not as expected", 1, pool.getIdleConnections());
+
+ try (Connection connection = ds.getConnection()) {
+ unwrap2 = connection.unwrap(Connection.class);
+ assertSame(unwrap, unwrap2);
+ assertSame("Second total connections not as expected", 1, pool.getTotalConnections());
+ assertSame("Second idle connections not as expected", 0, pool.getIdleConnections());
+ }
+
+ quietlySleep(TimeUnit.SECONDS.toMillis(2));
+
+ try (Connection connection = ds.getConnection()) {
+ unwrap2 = connection.unwrap(Connection.class);
+ assertNotSame("Expected a different connection", unwrap, unwrap2);
+ }
+
+ assertSame("Post total connections not as expected", 1, pool.getTotalConnections());
+ assertSame("Post idle connections not as expected", 1, pool.getIdleConnections());
+ }
+ finally {
+ setConfigUnitTest(false);
+ }
+ }
+
+ @Test
+ public void testMaxLifetime2() throws Exception
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(1);
+ config.setConnectionTimeout(2500);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "100");
+
+ setConfigUnitTest(true);
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ ds.setMaxLifetime(700);
+
+ HikariPool pool = getPool(ds);
+ assertSame("Total connections not as expected", 0, pool.getTotalConnections());
+ assertSame("Idle connections not as expected", 0, pool.getIdleConnections());
+
+ Connection unwrap;
+ Connection unwrap2;
+ try (Connection connection = ds.getConnection()) {
+ unwrap = connection.unwrap(Connection.class);
+ assertNotNull(connection);
+
+ assertSame("Second total connections not as expected", 1, pool.getTotalConnections());
+ assertSame("Second idle connections not as expected", 0, pool.getIdleConnections());
+ }
+
+ assertSame("Idle connections not as expected", 1, pool.getIdleConnections());
+
+ try (Connection connection = ds.getConnection()) {
+ unwrap2 = connection.unwrap(Connection.class);
+ assertSame(unwrap, unwrap2);
+ assertSame("Second total connections not as expected", 1, pool.getTotalConnections());
+ assertSame("Second idle connections not as expected", 0, pool.getIdleConnections());
+ }
+
+ quietlySleep(800);
+
+ try (Connection connection = ds.getConnection()) {
+ unwrap2 = connection.unwrap(Connection.class);
+ assertNotSame("Expected a different connection", unwrap, unwrap2);
+ }
+
+ assertSame("Post total connections not as expected", 1, pool.getTotalConnections());
+ assertSame("Post idle connections not as expected", 1, pool.getIdleConnections());
+ }
+ finally {
+ setConfigUnitTest(false);
+ }
+ }
+
+ @Test
+ public void testDoubleClose() throws Exception
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(1);
+ config.setMaximumPoolSize(1);
+ config.setConnectionTimeout(2500);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config);
+ Connection connection = ds.getConnection()) {
+ connection.close();
+
+ // should no-op
+ connection.abort(null);
+
+ assertTrue("Connection should have closed", connection.isClosed());
+ assertFalse("Connection should have closed", connection.isValid(5));
+ assertTrue("Expected to contain ClosedConnection, but was " + connection, connection.toString().contains("ClosedConnection"));
+ }
+ }
+
+ @Test
+ public void testEviction() throws Exception
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(5);
+ config.setConnectionTimeout(2500);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ Connection connection = ds.getConnection();
+
+ HikariPool pool = getPool(ds);
+ assertEquals(1, pool.getTotalConnections());
+ ds.evictConnection(connection);
+ assertEquals(0, pool.getTotalConnections());
+ }
+ }
+
+ @Test
+ public void testBackfill() throws Exception
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(1);
+ config.setMaximumPoolSize(4);
+ config.setConnectionTimeout(1000);
+ config.setInitializationFailTimeout(Long.MAX_VALUE);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ StubConnection.slowCreate = true;
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+
+ HikariPool pool = getPool(ds);
+ quietlySleep(1250);
+
+ assertSame("Total connections not as expected", 1, pool.getTotalConnections());
+ assertSame("Idle connections not as expected", 1, pool.getIdleConnections());
+
+ // This will take the pool down to zero
+ try (Connection connection = ds.getConnection()) {
+ assertNotNull(connection);
+
+ assertSame("Total connections not as expected", 1, pool.getTotalConnections());
+ assertSame("Idle connections not as expected", 0, pool.getIdleConnections());
+
+ PreparedStatement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?");
+ assertNotNull(statement);
+
+ ResultSet resultSet = statement.executeQuery();
+ assertNotNull(resultSet);
+
+ try {
+ statement.getMaxFieldSize();
+ fail();
+ }
+ catch (Exception e) {
+ assertSame(SQLException.class, e.getClass());
+ }
+
+ pool.logPoolState("testBackfill() before close...");
+
+ // The connection will be ejected from the pool here
+ }
+
+ assertSame("Total connections not as expected", 0, pool.getTotalConnections());
+
+ pool.logPoolState("testBackfill() after close...");
+
+ quietlySleep(1250);
+
+ assertSame("Total connections not as expected", 1, pool.getTotalConnections());
+ assertSame("Idle connections not as expected", 1, pool.getIdleConnections());
+ }
+ finally {
+ StubConnection.slowCreate = false;
+ }
+ }
+
+ @Test
+ public void testMaximumPoolLimit() throws Exception
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(1);
+ config.setMaximumPoolSize(4);
+ config.setConnectionTimeout(20000);
+ config.setInitializationFailTimeout(0);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ final AtomicReference<Exception> ref = new AtomicReference<>();
+
+ StubConnection.count.set(0); // reset counter
+
+ try (final HikariDataSource ds = new HikariDataSource(config)) {
+
+ final HikariPool pool = getPool(ds);
+
+ Thread[] threads = new Thread[20];
+ for (int i = 0; i < threads.length; i++) {
+ threads[i] = new Thread(new Runnable() {
+ @Override
+ public void run()
+ {
+ try {
+ pool.logPoolState("Before acquire ");
+ try (Connection connection = ds.getConnection()) {
+ pool.logPoolState("After acquire ");
+ quietlySleep(500);
+ }
+ }
+ catch (Exception e) {
+ ref.set(e);
+ }
+ }
+ });
+ }
+
+ for (int i = 0; i < threads.length; i++) {
+ threads[i].start();
+ }
+
+ for (int i = 0; i < threads.length; i++) {
+ threads[i].join();
+ }
+
+ pool.logPoolState("before check ");
+ assertNull((ref.get() != null ? ref.get().toString() : ""), ref.get());
+ assertSame("StubConnection count not as expected", 4, StubConnection.count.get());
+ }
+ }
+
+ @Test
+ public void testOldDriver() throws Exception
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(1);
+ config.setMaximumPoolSize(1);
+ config.setConnectionTimeout(2500);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ StubConnection.oldDriver = true;
+ StubStatement.oldDriver = true;
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ quietlySleep(500);
+
+ try (Connection connection = ds.getConnection()) {
+ // close
+ }
+
+ quietlySleep(500);
+ try (Connection connection = ds.getConnection()) {
+ // close
+ }
+ }
+ finally {
+ StubConnection.oldDriver = false;
+ StubStatement.oldDriver = false;
+ }
+ }
+
+ @Test
+ public void testSuspendResume() throws Exception
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(3);
+ config.setMaximumPoolSize(3);
+ config.setConnectionTimeout(2500);
+ config.setAllowPoolSuspension(true);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (final HikariDataSource ds = new HikariDataSource(config)) {
+ HikariPool pool = getPool(ds);
+ while (pool.getTotalConnections() < 3) {
+ quietlySleep(50);
+ }
+
+ Thread t = new Thread(new Runnable() {
+ @Override
+ public void run()
+ {
+ try {
+ ds.getConnection();
+ ds.getConnection();
+ }
+ catch (Exception e) {
+ fail();
+ }
+ }
+ });
+
+ try (Connection c3 = ds.getConnection()) {
+ assertEquals(2, pool.getIdleConnections());
+
+ pool.suspendPool();
+ t.start();
+
+ quietlySleep(500);
+ assertEquals(2, pool.getIdleConnections());
+ }
+ assertEquals(3, pool.getIdleConnections());
+ pool.resumePool();
+ quietlySleep(500);
+ assertEquals(1, pool.getIdleConnections());
+ }
+ }
+
+ @Test
+ public void testInitializationFailure1() throws SQLException
+ {
+ StubDataSource stubDataSource = new StubDataSource();
+ stubDataSource.setThrowException(new SQLException("Connection refused"));
+
+ try (HikariDataSource ds = newHikariDataSource()) {
+ ds.setMinimumIdle(1);
+ ds.setMaximumPoolSize(1);
+ ds.setConnectionTimeout(2500);
+ ds.setConnectionTestQuery("VALUES 1");
+ ds.setDataSource(stubDataSource);
+
+ try (Connection c = ds.getConnection()) {
+ fail("Initialization should have failed");
+ }
+ catch (SQLException e) {
+ // passed
+ }
+ }
+ }
+
+ @Test
+ public void testInitializationFailure2() throws SQLException
+ {
+ StubDataSource stubDataSource = new StubDataSource();
+ stubDataSource.setThrowException(new SQLException("Connection refused"));
+
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(1);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSource(stubDataSource);
+
+ try (HikariDataSource ds = new HikariDataSource(config);
+ Connection c = ds.getConnection()) {
+ fail("Initialization should have failed");
+ }
+ catch (PoolInitializationException e) {
+ // passed
+ }
+ }
+
+ @Test
+ public void testInvalidConnectionTestQuery()
+ {
+ class BadConnection extends StubConnection {
+ /** {@inheritDoc} */
+ @Override
+ public Statement createStatement() throws SQLException
+ {
+ throw new SQLException("Simulated exception in createStatement()");
+ }
+ }
+
+ StubDataSource stubDataSource = new StubDataSource() {
+ /** {@inheritDoc} */
+ @Override
+ public Connection getConnection() throws SQLException
+ {
+ return new BadConnection();
+ }
+ };
+
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(1);
+ config.setMaximumPoolSize(2);
+ config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(3));
+ config.setConnectionTestQuery("VALUES 1");
+ config.setInitializationFailTimeout(TimeUnit.SECONDS.toMillis(2));
+ config.setDataSource(stubDataSource);
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ try (Connection c = ds.getConnection()) {
+ fail("getConnection() should have failed");
+ }
+ catch (SQLException e) {
+ assertSame("Simulated exception in createStatement()", e.getNextException().getMessage());
+ }
+ }
+ catch (PoolInitializationException e) {
+ assertSame("Simulated exception in createStatement()", e.getCause().getMessage());
+ }
+
+ config.setInitializationFailTimeout(0);
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ fail("Initialization should have failed");
+ }
+ catch (PoolInitializationException e) {
+ // passed
+ }
+ }
+
+ @Test
+ public void testPopulationSlowAcquisition() throws InterruptedException, SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMaximumPoolSize(20);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "1000");
+
+ StubConnection.slowCreate = true;
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs");
+
+ ds.setIdleTimeout(3000);
+
+ SECONDS.sleep(2);
+
+ HikariPool pool = getPool(ds);
+ assertSame("Total connections not as expected", 2, pool.getTotalConnections());
+ assertSame("Idle connections not as expected", 2, pool.getIdleConnections());
+
+ try (Connection connection = ds.getConnection()) {
+ assertNotNull(connection);
+
+ SECONDS.sleep(20);
+
+ assertSame("Second total connections not as expected", 20, pool.getTotalConnections());
+ assertSame("Second idle connections not as expected", 19, pool.getIdleConnections());
+ }
+
+ assertSame("Idle connections not as expected", 20, pool.getIdleConnections());
+
+ SECONDS.sleep(5);
+
+ assertSame("Third total connections not as expected", 20, pool.getTotalConnections());
+ assertSame("Third idle connections not as expected", 20, pool.getIdleConnections());
+ }
+ finally {
+ StubConnection.slowCreate = false;
+ }
+ }
+
+ @Test
+ public void testMinimumIdleZero() throws SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(5);
+ config.setConnectionTimeout(1000L);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config);
+ Connection connection = ds.getConnection()) {
+ // passed
+ }
+ catch (SQLTransientConnectionException sqle) {
+ fail("Failed to obtain connection");
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestElf.java b/src/test/java/com/zaxxer/hikari/pool/TestElf.java
new file mode 100644
index 0000000..6438b11
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/TestElf.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.pool;
+
+import java.io.PrintStream;
+import java.lang.reflect.Field;
+import java.sql.Connection;
+import java.util.HashMap;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.layout.CsvLogEventLayout;
+import org.apache.logging.slf4j.Log4jLogger;
+import org.slf4j.LoggerFactory;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+/**
+ * Utility methods for testing.
+ *
+ * @author Brett Wooldridge
+ */
+public final class TestElf
+{
+ private TestElf() {
+ // default constructor
+ }
+
+ public static HikariPool getPool(HikariDataSource ds)
+ {
+ try {
+ Field field = ds.getClass().getDeclaredField("pool");
+ field.setAccessible(true);
+ return (HikariPool) field.get(ds);
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static HashMap<Object, HikariPool> getMultiPool(HikariDataSource ds)
+ {
+ try {
+ Field field = ds.getClass().getDeclaredField("multiPool");
+ field.setAccessible(true);
+ return (HashMap<Object, HikariPool>) field.get(ds);
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static boolean getConnectionCommitDirtyState(Connection connection)
+ {
+ try {
+ Field field = ProxyConnection.class.getDeclaredField("isCommitStateDirty");
+ field.setAccessible(true);
+ return field.getBoolean(connection);
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void setConfigUnitTest(boolean unitTest)
+ {
+ try {
+ Field field = HikariConfig.class.getDeclaredField("unitTest");
+ field.setAccessible(true);
+ field.setBoolean(null, unitTest);
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void setSlf4jTargetStream(Class<?> clazz, PrintStream stream)
+ {
+ try {
+ Log4jLogger log4Jlogger = (Log4jLogger) LoggerFactory.getLogger(clazz);
+
+ Field field = clazz.getClassLoader().loadClass("org.apache.logging.slf4j.Log4jLogger").getDeclaredField("logger");
+ field.setAccessible(true);
+
+ Logger logger = (Logger) field.get(log4Jlogger);
+ if (logger.getAppenders().containsKey("string")) {
+ Appender appender = logger.getAppenders().get("string");
+ logger.removeAppender(appender);
+ }
+
+ logger.addAppender(new StringAppender("string", stream));
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void setSlf4jLogLevel(Class<?> clazz, Level logLevel)
+ {
+ try {
+ Log4jLogger log4Jlogger = (Log4jLogger) LoggerFactory.getLogger(clazz);
+
+ Field field = clazz.getClassLoader().loadClass("org.apache.logging.slf4j.Log4jLogger").getDeclaredField("logger");
+ field.setAccessible(true);
+
+ Logger logger = (Logger) field.get(log4Jlogger);
+ logger.setLevel(logLevel);
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static HikariConfig newHikariConfig()
+ {
+ final StackTraceElement callerStackTrace = Thread.currentThread().getStackTrace()[2];
+
+ String poolName = callerStackTrace.getMethodName();
+ if ("setup".equals(poolName)) {
+ poolName = callerStackTrace.getClassName();
+ }
+
+ final HikariConfig config = new HikariConfig();
+ config.setPoolName(poolName);
+ return config;
+ }
+
+ public static HikariDataSource newHikariDataSource()
+ {
+ final StackTraceElement callerStackTrace = Thread.currentThread().getStackTrace()[2];
+
+ String poolName = callerStackTrace.getMethodName();
+ if ("setup".equals(poolName)) {
+ poolName = callerStackTrace.getClassName();
+ }
+
+ final HikariDataSource ds = new HikariDataSource();
+ ds.setPoolName(poolName);
+ return ds;
+ }
+
+ private static class StringAppender extends AbstractAppender
+ {
+ private PrintStream stream;
+
+ StringAppender(String name, PrintStream stream)
+ {
+ super(name, null, CsvLogEventLayout.createDefaultLayout());
+ this.stream = stream;
+ }
+
+ @Override
+ public void append(LogEvent event)
+ {
+ stream.println(event.getMessage().getFormattedMessage());
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestHibernate.java b/src/test/java/com/zaxxer/hikari/pool/TestHibernate.java
new file mode 100644
index 0000000..c1d6bae
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/TestHibernate.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.pool;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.sql.Connection;
+import java.util.Properties;
+
+import org.hibernate.service.UnknownUnwrapTypeException;
+import org.junit.Test;
+
+import com.zaxxer.hikari.hibernate.HikariConnectionProvider;
+
+public class TestHibernate
+{
+ @Test
+ public void testConnectionProvider() throws Exception
+ {
+ HikariConnectionProvider provider = new HikariConnectionProvider();
+
+ Properties props = new Properties();
+ props.load(getClass().getResourceAsStream("/hibernate.properties"));
+
+ provider.configure(props);
+ Connection connection = provider.getConnection();
+ provider.closeConnection(connection);
+
+ assertNotNull(provider.unwrap(HikariConnectionProvider.class));
+ assertFalse(provider.supportsAggressiveRelease());
+
+ try {
+ provider.unwrap(TestHibernate.class);
+ fail("Expected exception");
+ }
+ catch (UnknownUnwrapTypeException e) {
+ }
+
+ provider.stop();
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestJNDI.java b/src/test/java/com/zaxxer/hikari/pool/TestJNDI.java
new file mode 100644
index 0000000..b5cd40c
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/TestJNDI.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.zaxxer.hikari.pool;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+
+import org.junit.Test;
+import org.osjava.sj.jndi.AbstractContext;
+
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.HikariJNDIFactory;
+import com.zaxxer.hikari.mocks.StubDataSource;
+
+public class TestJNDI
+{
+ @Test
+ public void testJndiLookup1() throws Exception
+ {
+ HikariJNDIFactory jndi = new HikariJNDIFactory();
+ Reference ref = new Reference("javax.sql.DataSource");
+ ref.add(new BogusRef("driverClassName", "com.zaxxer.hikari.mocks.StubDriver"));
+ ref.add(new BogusRef("jdbcUrl", "jdbc:stub"));
+ ref.add(new BogusRef("username", "foo"));
+ ref.add(new BogusRef("password", "foo"));
+ ref.add(new BogusRef("minimumIdle", "0"));
+ ref.add(new BogusRef("maxLifetime", "30000"));
+ ref.add(new BogusRef("maximumPoolSize", "10"));
+ ref.add(new BogusRef("dataSource.loginTimeout", "10"));
+ Context nameCtx = new BogusContext();
+
+ try (HikariDataSource ds = (HikariDataSource) jndi.getObjectInstance(ref, null, nameCtx, null)) {
+ assertNotNull(ds);
+ assertEquals("foo", ds.getUsername());
+ }
+ }
+
+ @Test
+ public void testJndiLookup2() throws Exception
+ {
+ HikariJNDIFactory jndi = new HikariJNDIFactory();
+ Reference ref = new Reference("javax.sql.DataSource");
+ ref.add(new BogusRef("dataSourceJNDI", "java:comp/env/HikariDS"));
+ ref.add(new BogusRef("driverClassName", "com.zaxxer.hikari.mocks.StubDriver"));
+ ref.add(new BogusRef("jdbcUrl", "jdbc:stub"));
+ ref.add(new BogusRef("username", "foo"));
+ ref.add(new BogusRef("password", "foo"));
+ ref.add(new BogusRef("minimumIdle", "0"));
+ ref.add(new BogusRef("maxLifetime", "30000"));
+ ref.add(new BogusRef("maximumPoolSize", "10"));
+ ref.add(new BogusRef("dataSource.loginTimeout", "10"));
+ Context nameCtx = new BogusContext2();
+
+ try (HikariDataSource ds = (HikariDataSource) jndi.getObjectInstance(ref, null, nameCtx, null)) {
+ assertNotNull(ds);
+ assertEquals("foo", ds.getUsername());
+ }
+ }
+
+ @Test
+ public void testJndiLookup3() throws Exception
+ {
+ HikariJNDIFactory jndi = new HikariJNDIFactory();
+
+ Reference ref = new Reference("javax.sql.DataSource");
+ ref.add(new BogusRef("dataSourceJNDI", "java:comp/env/HikariDS"));
+ try {
+ jndi.getObjectInstance(ref, null, null, null);
+ fail();
+ }
+ catch (RuntimeException e) {
+ assertTrue(e.getMessage().contains("JNDI context does not found"));
+ }
+ }
+
+ private class BogusContext extends AbstractContext
+ {
+ @Override
+ public Context createSubcontext(Name name) throws NamingException
+ {
+ return null;
+ }
+
+ @Override
+ public Object lookup(String name) throws NamingException
+ {
+ final HikariDataSource ds = new HikariDataSource();
+ ds.setPoolName("TestJNDI");
+ return ds;
+ }
+ }
+
+ private class BogusContext2 extends AbstractContext
+ {
+ @Override
+ public Context createSubcontext(Name name) throws NamingException
+ {
+ return null;
+ }
+
+ @Override
+ public Object lookup(String name) throws NamingException
+ {
+ return new StubDataSource();
+ }
+ }
+
+ private class BogusRef extends RefAddr
+ {
+ private static final long serialVersionUID = 1L;
+
+ private String content;
+ BogusRef(String type, String content)
+ {
+ super(type);
+ this.content = content;
+ }
+
+ @Override
+ public Object getContent()
+ {
+ return content;
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestMBean.java b/src/test/java/com/zaxxer/hikari/pool/TestMBean.java
new file mode 100644
index 0000000..55ba733
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/TestMBean.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+
+import java.sql.SQLException;
+
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+public class TestMBean
+{
+ @Test
+ public void testMBeanRegistration() throws SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(1);
+ config.setRegisterMbeans(true);
+ config.setConnectionTimeout(2800);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ // Close immediately
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestMetrics.java b/src/test/java/com/zaxxer/hikari/pool/TestMetrics.java
new file mode 100644
index 0000000..fcba58e
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/TestMetrics.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource;
+import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.SortedMap;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Test;
+
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.health.HealthCheck.Result;
+import com.codahale.metrics.health.HealthCheckRegistry;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
+import com.zaxxer.hikari.metrics.dropwizard.CodahaleMetricsTrackerFactory;
+import com.zaxxer.hikari.util.UtilityElf;
+
+import shaded.org.codehaus.plexus.interpolation.os.Os;
+
+/**
+ * Test HikariCP/CodaHale metrics integration.
+ *
+ * @author Brett Wooldridge
+ */
+public class TestMetrics
+{
+ @Test
+ public void testMetricWait() throws SQLException
+ {
+ MetricRegistry metricRegistry = new MetricRegistry();
+
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(1);
+ config.setMaximumPoolSize(1);
+ config.setMetricRegistry(metricRegistry);
+ config.setInitializationFailTimeout(Long.MAX_VALUE);
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ ds.getConnection().close();
+
+ Timer timer = metricRegistry.getTimers(new MetricFilter() {
+ /** {@inheritDoc} */
+ @Override
+ public boolean matches(String name, Metric metric)
+ {
+ return "testMetricWait.pool.Wait".equals(MetricRegistry.name("testMetricWait", "pool", "Wait"));
+ }
+ }).values().iterator().next();
+
+ assertEquals(1, timer.getCount());
+ assertTrue(timer.getMeanRate() > 0.0);
+ }
+ }
+
+ @Test
+ public void testMetricUsage() throws SQLException
+ {
+ assumeFalse(Os.isFamily(Os.FAMILY_WINDOWS));
+ MetricRegistry metricRegistry = new MetricRegistry();
+
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(1);
+ config.setMaximumPoolSize(1);
+ config.setMetricRegistry(metricRegistry);
+ config.setInitializationFailTimeout(0);
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ try (Connection connection = ds.getConnection()) {
+ UtilityElf.quietlySleep(250L);
+ }
+
+ Histogram histo = metricRegistry.getHistograms(new MetricFilter() {
+ /** {@inheritDoc} */
+ @Override
+ public boolean matches(String name, Metric metric)
+ {
+ return name.equals(MetricRegistry.name("testMetricUsage", "pool", "Usage"));
+ }
+ }).values().iterator().next();
+
+ assertEquals(1, histo.getCount());
+ double seventyFifth = histo.getSnapshot().get75thPercentile();
+ assertTrue("Seventy-fith percentile less than 250ms: " + seventyFifth, seventyFifth >= 250.0);
+ }
+ }
+
+ @Test
+ public void testHealthChecks() throws Exception
+ {
+ MetricRegistry metricRegistry = new MetricRegistry();
+ HealthCheckRegistry healthRegistry = new HealthCheckRegistry();
+
+ HikariConfig config = newHikariConfig();
+ config.setMaximumPoolSize(10);
+ config.setMetricRegistry(metricRegistry);
+ config.setHealthCheckRegistry(healthRegistry);
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+ config.addHealthCheckProperty("connectivityCheckTimeoutMs", "1000");
+ config.addHealthCheckProperty("expected99thPercentileMs", "100");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ quietlySleep(TimeUnit.SECONDS.toMillis(2));
+
+ try (Connection connection = ds.getConnection()) {
+ // close immediately
+ }
+
+ try (Connection connection = ds.getConnection()) {
+ // close immediately
+ }
+
+ SortedMap<String, Result> healthChecks = healthRegistry.runHealthChecks();
+
+ Result connectivityResult = healthChecks.get("testHealthChecks.pool.ConnectivityCheck");
+ assertTrue(connectivityResult.isHealthy());
+
+ Result slaResult = healthChecks.get("testHealthChecks.pool.Connection99Percent");
+ assertTrue(slaResult.isHealthy());
+ }
+ }
+
+ @Test
+ public void testSetters1() throws Exception
+ {
+ try (HikariDataSource ds = newHikariDataSource()) {
+ ds.setMaximumPoolSize(1);
+ ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ MetricRegistry metricRegistry = new MetricRegistry();
+ HealthCheckRegistry healthRegistry = new HealthCheckRegistry();
+
+ try {
+ try (Connection connection = ds.getConnection()) {
+ // close immediately
+ }
+
+ // After the pool as started, we can only set them once...
+ ds.setMetricRegistry(metricRegistry);
+ ds.setHealthCheckRegistry(healthRegistry);
+
+ // and never again...
+ ds.setMetricRegistry(metricRegistry);
+ fail("Should not have been allowed to set registry after pool started");
+ }
+ catch (IllegalStateException ise) {
+ // pass
+ try {
+ ds.setHealthCheckRegistry(healthRegistry);
+ fail("Should not have been allowed to set registry after pool started");
+ }
+ catch (IllegalStateException ise2) {
+ // pass
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testSetters2() throws Exception
+ {
+ try (HikariDataSource ds = newHikariDataSource()) {
+ ds.setMaximumPoolSize(1);
+ ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ MetricRegistry metricRegistry = new MetricRegistry();
+ HealthCheckRegistry healthRegistry = new HealthCheckRegistry();
+
+ ds.setMetricRegistry(metricRegistry);
+ ds.setHealthCheckRegistry(healthRegistry);
+
+ // before the pool is started, we can set it any number of times...
+ ds.setMetricRegistry(metricRegistry);
+ ds.setHealthCheckRegistry(healthRegistry);
+
+ try (Connection connection = ds.getConnection()) {
+
+ // after the pool is started, we cannot set it any more
+ ds.setMetricRegistry(metricRegistry);
+ fail("Should not have been allowed to set registry after pool started");
+ }
+ catch (IllegalStateException ise) {
+ // pass
+ }
+ }
+ }
+
+ @Test
+ public void testSetters3() throws Exception
+ {
+ try (HikariDataSource ds = newHikariDataSource()) {
+ ds.setMaximumPoolSize(1);
+ ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ MetricRegistry metricRegistry = new MetricRegistry();
+ MetricsTrackerFactory metricsTrackerFactory = new CodahaleMetricsTrackerFactory(metricRegistry);
+
+ try (Connection connection = ds.getConnection()) {
+
+ // After the pool as started, we can only set them once...
+ ds.setMetricsTrackerFactory(metricsTrackerFactory);
+
+ // and never again...
+ ds.setMetricsTrackerFactory(metricsTrackerFactory);
+ fail("Should not have been allowed to set metricsTrackerFactory after pool started");
+ }
+ catch (IllegalStateException ise) {
+ // pass
+ try {
+ // and never again... (even when calling another method)
+ ds.setMetricRegistry(metricRegistry);
+ fail("Should not have been allowed to set registry after pool started");
+ }
+ catch (IllegalStateException ise2) {
+ // pass
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testSetters4() throws Exception
+ {
+ try (HikariDataSource ds = newHikariDataSource()) {
+ ds.setMaximumPoolSize(1);
+ ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ MetricRegistry metricRegistry = new MetricRegistry();
+
+ // before the pool is started, we can set it any number of times using either setter
+ ds.setMetricRegistry(metricRegistry);
+ ds.setMetricRegistry(metricRegistry);
+ ds.setMetricRegistry(metricRegistry);
+
+ try (Connection connection = ds.getConnection()) {
+
+ // after the pool is started, we cannot set it any more
+ ds.setMetricRegistry(metricRegistry);
+ fail("Should not have been allowed to set registry after pool started");
+ }
+ catch (IllegalStateException ise) {
+ // pass
+ }
+ }
+ }
+
+ @Test
+ public void testSetters5() throws Exception
+ {
+ try (HikariDataSource ds = newHikariDataSource()) {
+ ds.setMaximumPoolSize(1);
+ ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ MetricRegistry metricRegistry = new MetricRegistry();
+ MetricsTrackerFactory metricsTrackerFactory = new CodahaleMetricsTrackerFactory(metricRegistry);
+
+ // before the pool is started, we can set it any number of times using either setter
+ ds.setMetricsTrackerFactory(metricsTrackerFactory);
+ ds.setMetricsTrackerFactory(metricsTrackerFactory);
+ ds.setMetricsTrackerFactory(metricsTrackerFactory);
+
+ try (Connection connection = ds.getConnection()) {
+
+ // after the pool is started, we cannot set it any more
+ ds.setMetricsTrackerFactory(metricsTrackerFactory);
+ fail("Should not have been allowed to set registry factory after pool started");
+ }
+ catch (IllegalStateException ise) {
+ // pass
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestPropertySetter.java b/src/test/java/com/zaxxer/hikari/pool/TestPropertySetter.java
new file mode 100644
index 0000000..fb10446
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/TestPropertySetter.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.sql.DataSource;
+
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.util.PropertyElf;
+
+public class TestPropertySetter
+{
+ @Test
+ public void testProperty1() throws Exception
+ {
+ Properties propfile1 = new Properties();
+ propfile1.load(TestPropertySetter.class.getResourceAsStream("/propfile1.properties"));
+ HikariConfig config = new HikariConfig(propfile1);
+ config.validate();
+
+ assertEquals(5, config.getMinimumIdle());
+ assertEquals("SELECT 1", config.getConnectionTestQuery());
+ }
+
+ @Test
+ public void testProperty2() throws Exception
+ {
+ Properties propfile2 = new Properties();
+ propfile2.load(TestPropertySetter.class.getResourceAsStream("/propfile2.properties"));
+ HikariConfig config = new HikariConfig(propfile2);
+ config.validate();
+
+ Class<?> clazz = this.getClass().getClassLoader().loadClass(config.getDataSourceClassName());
+ DataSource dataSource = (DataSource) clazz.newInstance();
+ PropertyElf.setTargetFromProperties(dataSource, config.getDataSourceProperties());
+ }
+
+ @Test
+ public void testObjectProperty() throws Exception
+ {
+ HikariConfig config = newHikariConfig();
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+ PrintWriter writer = new PrintWriter(new ByteArrayOutputStream());
+ config.addDataSourceProperty("logWriter", writer);
+
+ Class<?> clazz = this.getClass().getClassLoader().loadClass(config.getDataSourceClassName());
+ DataSource dataSource = (DataSource) clazz.newInstance();
+ PropertyElf.setTargetFromProperties(dataSource, config.getDataSourceProperties());
+
+ assertSame(PrintWriter.class, dataSource.getLogWriter().getClass());
+ }
+
+ @Test
+ public void testPropertyUpperCase() throws Exception
+ {
+ Properties propfile3 = new Properties();
+ propfile3.load(TestPropertySetter.class.getResourceAsStream("/propfile3.properties"));
+ HikariConfig config = new HikariConfig(propfile3);
+ config.validate();
+
+ Class<?> clazz = this.getClass().getClassLoader().loadClass(config.getDataSourceClassName());
+ DataSource dataSource = (DataSource) clazz.newInstance();
+ PropertyElf.setTargetFromProperties(dataSource, config.getDataSourceProperties());
+ }
+
+ @Test
+ public void testGetPropertyNames() throws Exception
+ {
+ Set<String> propertyNames = PropertyElf.getPropertyNames(HikariConfig.class);
+ assertTrue(propertyNames.contains("dataSourceClassName"));
+ }
+
+ @Test
+ public void testSetNonExistantPropertyName() throws Exception
+ {
+ try {
+ Properties props = new Properties();
+ props.put("what", "happened");
+ PropertyElf.setTargetFromProperties(new HikariConfig(), props);
+ fail();
+ }
+ catch (RuntimeException e) {
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestProxies.java b/src/test/java/com/zaxxer/hikari/pool/TestProxies.java
new file mode 100644
index 0000000..96f4066
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/TestProxies.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.mocks.StubConnection;
+import com.zaxxer.hikari.mocks.StubStatement;
+
+public class TestProxies
+{
+ @Test
+ public void testProxyCreation() throws SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(1);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ Connection conn = ds.getConnection();
+
+ assertNotNull(conn.createStatement(ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE));
+ assertNotNull(conn.createStatement(ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.HOLD_CURSORS_OVER_COMMIT));
+ assertNotNull(conn.prepareCall("some sql"));
+ assertNotNull(conn.prepareCall("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE));
+ assertNotNull(conn.prepareCall("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.HOLD_CURSORS_OVER_COMMIT));
+ assertNotNull(conn.prepareStatement("some sql", PreparedStatement.NO_GENERATED_KEYS));
+ assertNotNull(conn.prepareStatement("some sql", new int[3]));
+ assertNotNull(conn.prepareStatement("some sql", new String[3]));
+ assertNotNull(conn.prepareStatement("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE));
+ assertNotNull(conn.prepareStatement("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.HOLD_CURSORS_OVER_COMMIT));
+ assertNotNull(conn.toString());
+
+ assertTrue(conn.isWrapperFor(Connection.class));
+ assertTrue(conn.isValid(10));
+ assertFalse(conn.isClosed());
+ assertTrue(conn.unwrap(StubConnection.class) instanceof StubConnection);
+ try {
+ conn.unwrap(TestProxies.class);
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+ }
+ }
+
+ @Test
+ public void testStatementProxy() throws SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(1);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ Connection conn = ds.getConnection();
+
+ PreparedStatement stmt = conn.prepareStatement("some sql");
+ stmt.executeQuery();
+ stmt.executeQuery("some sql");
+ assertFalse(stmt.isClosed());
+ assertNotNull(stmt.getGeneratedKeys());
+ assertNotNull(stmt.getResultSet());
+ assertNotNull(stmt.getConnection());
+ assertTrue(stmt.unwrap(StubStatement.class) instanceof StubStatement);
+ try {
+ stmt.unwrap(TestProxies.class);
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+ }
+ }
+
+ @Test
+ public void testStatementExceptions() throws SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(1);
+ config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(1));
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ Connection conn = ds.getConnection();
+ StubConnection stubConnection = conn.unwrap(StubConnection.class);
+ stubConnection.throwException = true;
+
+ try {
+ conn.createStatement();
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.createStatement(0, 0);
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.createStatement(0, 0, 0);
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.prepareCall("");
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.prepareCall("", 0, 0);
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.prepareCall("", 0, 0, 0);
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.prepareStatement("");
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.prepareStatement("", 0);
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.prepareStatement("", new int[0]);
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.prepareStatement("", new String[0]);
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.prepareStatement("", 0, 0);
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.prepareStatement("", 0, 0, 0);
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+ }
+ }
+
+ @Test
+ public void testOtherExceptions() throws SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(1);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ try (Connection conn = ds.getConnection()) {
+ StubConnection stubConnection = conn.unwrap(StubConnection.class);
+ stubConnection.throwException = true;
+
+ try {
+ conn.setTransactionIsolation(Connection.TRANSACTION_NONE);
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.isReadOnly();
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.setReadOnly(false);
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.setCatalog("");
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.setAutoCommit(false);
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.clearWarnings();
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.isValid(0);
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.isWrapperFor(getClass());
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.unwrap(getClass());
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ conn.close();
+ fail();
+ }
+ catch (SQLException e) {
+ // pass
+ }
+
+ try {
+ assertFalse(conn.isValid(0));
+ }
+ catch (SQLException e) {
+ fail();
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestValidation.java b/src/test/java/com/zaxxer/hikari/pool/TestValidation.java
new file mode 100644
index 0000000..46ba779
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/TestValidation.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.setSlf4jTargetStream;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+
+/**
+ * @author Brett Wooldridge
+ */
+public class TestValidation
+{
+ @Test
+ public void validateLoadProperties()
+ {
+ System.setProperty("hikaricp.configurationFile", "/propfile1.properties");
+ HikariConfig config = newHikariConfig();
+ System.clearProperty("hikaricp.configurationFile");
+ assertEquals(5, config.getMinimumIdle());
+ }
+
+ @Test
+ public void validateMissingProperties()
+ {
+ try {
+ HikariConfig config = new HikariConfig("missing");
+ config.validate();
+ }
+ catch (IllegalArgumentException ise) {
+ assertTrue(ise.getMessage().contains("property file"));
+ }
+ }
+
+ @Test
+ public void validateMissingDS()
+ {
+ try {
+ HikariConfig config = newHikariConfig();
+ config.validate();
+ fail();
+ }
+ catch (IllegalArgumentException ise) {
+ assertTrue(ise.getMessage().contains("dataSource or dataSourceClassName or jdbcUrl is required."));
+ }
+ }
+
+ @Test
+ public void validateMissingUrl()
+ {
+ try {
+ HikariConfig config = newHikariConfig();
+ config.setDriverClassName("com.zaxxer.hikari.mocks.StubDriver");
+ config.validate();
+ fail();
+ }
+ catch (IllegalArgumentException ise) {
+ assertTrue(ise.getMessage().contains("jdbcUrl is required with driverClassName"));
+ }
+ }
+
+ @Test
+ public void validateDriverAndUrl()
+ {
+ try {
+ HikariConfig config = newHikariConfig();
+ config.setDriverClassName("com.zaxxer.hikari.mocks.StubDriver");
+ config.setJdbcUrl("jdbc:stub");
+ config.validate();
+ }
+ catch (Throwable t) {
+ fail(t.getMessage());
+ }
+ }
+
+ @Test
+ public void validateBadDriver()
+ {
+ try {
+ HikariConfig config = newHikariConfig();
+ config.setDriverClassName("invalid");
+ config.validate();
+ fail();
+ }
+ catch (RuntimeException ise) {
+ assertTrue(ise.getMessage().contains("class of driverClassName "));
+ }
+ }
+
+ @Test
+ public void validateInvalidConnectionTimeout()
+ {
+ try {
+ HikariConfig config = newHikariConfig();
+ config.setConnectionTimeout(10L);
+ fail();
+ }
+ catch (IllegalArgumentException ise) {
+ assertTrue(ise.getMessage().contains("connectionTimeout cannot be less than 250ms"));
+ }
+ }
+
+ @Test
+ public void validateInvalidValidationTimeout()
+ {
+ try {
+ HikariConfig config = newHikariConfig();
+ config.setValidationTimeout(10L);
+ fail();
+ }
+ catch (IllegalArgumentException ise) {
+ assertTrue(ise.getMessage().contains("validationTimeout cannot be less than 250ms"));
+ }
+ }
+
+ @Test
+ public void validateInvalidIdleTimeout()
+ {
+ try {
+ HikariConfig config = newHikariConfig();
+ config.setIdleTimeout(-1L);
+ fail("negative idle timeout accepted");
+ }
+ catch (IllegalArgumentException ise) {
+ assertTrue(ise.getMessage().contains("idleTimeout cannot be negative"));
+ }
+ }
+
+ @Test
+ public void validateIdleTimeoutTooSmall()
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(baos, true);
+ setSlf4jTargetStream(HikariConfig.class, ps);
+
+ HikariConfig config = newHikariConfig();
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+ config.setIdleTimeout(TimeUnit.SECONDS.toMillis(5));
+ config.validate();
+ assertTrue(new String(baos.toByteArray()).contains("less than 10000ms"));
+ }
+
+ @Test
+ public void validateIdleTimeoutExceedsLifetime()
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(baos, true);
+ setSlf4jTargetStream(HikariConfig.class, ps);
+
+ HikariConfig config = newHikariConfig();
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+ config.setMaxLifetime(TimeUnit.MINUTES.toMillis(2));
+ config.setIdleTimeout(TimeUnit.MINUTES.toMillis(3));
+ config.validate();
+
+ String s = new String(baos.toByteArray());
+ assertTrue("idleTimeout is close to or more than maxLifetime, disabling it." + s + "*", s.contains("is close to or more than maxLifetime"));
+ }
+
+ @Test
+ public void validateInvalidMinIdle()
+ {
+ try {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(-1);
+ fail();
+ }
+ catch (IllegalArgumentException ise) {
+ assertTrue(ise.getMessage().contains("minimumIdle cannot be negative"));
+ }
+ }
+
+ @Test
+ public void validateInvalidMaxPoolSize()
+ {
+ try {
+ HikariConfig config = newHikariConfig();
+ config.setMaximumPoolSize(0);
+ fail();
+ }
+ catch (IllegalArgumentException ise) {
+ assertTrue(ise.getMessage().contains("maxPoolSize cannot be less than 1"));
+ }
+ }
+
+ @Test
+ public void validateInvalidLifetime()
+ {
+ try {
+ HikariConfig config = newHikariConfig();
+ config.setConnectionTimeout(Integer.MAX_VALUE);
+ config.setIdleTimeout(1000L);
+ config.setMaxLifetime(-1L);
+ config.setLeakDetectionThreshold(1000L);
+ config.validate();
+ fail();
+ }
+ catch (IllegalArgumentException ise) {
+ // pass
+ }
+ }
+
+ @Test
+ public void validateInvalidLeakDetection()
+ {
+ try {
+ HikariConfig config = newHikariConfig();
+ config.setLeakDetectionThreshold(1000L);
+ config.validate();
+ fail();
+ }
+ catch (IllegalArgumentException ise) {
+ // pass
+ }
+ }
+
+ @Test
+ public void validateZeroConnectionTimeout()
+ {
+ try {
+ HikariConfig config = newHikariConfig();
+ config.setConnectionTimeout(0);
+ config.validate();
+ assertEquals(Integer.MAX_VALUE, config.getConnectionTimeout());
+ }
+ catch (IllegalArgumentException ise) {
+ // pass
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/UnwrapTest.java b/src/test/java/com/zaxxer/hikari/pool/UnwrapTest.java
new file mode 100644
index 0000000..d13c8f4
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/UnwrapTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2013 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.getPool;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import org.junit.Test;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.mocks.StubConnection;
+import com.zaxxer.hikari.mocks.StubDataSource;
+
+/**
+ * @author Brett Wooldridge
+ */
+public class UnwrapTest
+{
+ @Test
+ public void testUnwrapConnection() throws SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(1);
+ config.setMaximumPoolSize(1);
+ config.setInitializationFailTimeout(0);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ ds.getConnection().close();
+ assertSame("Idle connections not as expected", 1, getPool(ds).getIdleConnections());
+
+ Connection connection = ds.getConnection();
+ assertNotNull(connection);
+
+ StubConnection unwrapped = connection.unwrap(StubConnection.class);
+ assertTrue("unwrapped connection is not instance of StubConnection: " + unwrapped, (unwrapped != null && unwrapped instanceof StubConnection));
+ }
+ }
+
+ @Test
+ public void testUnwrapDataSource() throws SQLException
+ {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(1);
+ config.setMaximumPoolSize(1);
+ config.setInitializationFailTimeout(0);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ StubDataSource unwrap = ds.unwrap(StubDataSource.class);
+ assertNotNull(unwrap);
+ assertTrue(unwrap instanceof StubDataSource);
+
+ assertTrue(ds.isWrapperFor(HikariDataSource.class));
+ assertTrue(ds.unwrap(HikariDataSource.class) instanceof HikariDataSource);
+
+ assertFalse(ds.isWrapperFor(getClass()));
+ try {
+ ds.unwrap(getClass());
+ }
+ catch (SQLException e) {
+ assertTrue(e.getMessage().contains("Wrapped DataSource"));
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/util/ClockSourceTest.java b/src/test/java/com/zaxxer/hikari/util/ClockSourceTest.java
new file mode 100644
index 0000000..2d0482a
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/util/ClockSourceTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2016 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.util;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import static java.util.concurrent.TimeUnit.DAYS;
+import static java.util.concurrent.TimeUnit.HOURS;
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+/**
+ *
+ * @author Brett Wooldridge
+ */
+public class ClockSourceTest
+{
+ @Test
+ public void testClockSourceDisplay()
+ {
+ ClockSource msSource = new ClockSource.MillisecondClockSource();
+
+ final long sTime = DAYS.toMillis(3) + HOURS.toMillis(9) + MINUTES.toMillis(24) + SECONDS.toMillis(18) + MILLISECONDS.toMillis(572);
+
+ final long eTime = DAYS.toMillis(4) + HOURS.toMillis(9) + MINUTES.toMillis(55) + SECONDS.toMillis(23) + MILLISECONDS.toMillis(777);
+ String ds1 = msSource.elapsedDisplayString0(sTime, eTime);
+ Assert.assertEquals("1d31m5s205ms", ds1);
+
+ final long eTime2 = DAYS.toMillis(3) + HOURS.toMillis(8) + MINUTES.toMillis(24) + SECONDS.toMillis(23) + MILLISECONDS.toMillis(777);
+ String ds2 = msSource.elapsedDisplayString0(sTime, eTime2);
+ Assert.assertEquals("-59m54s795ms", ds2);
+
+
+ ClockSource nsSource = new ClockSource.NanosecondClockSource();
+
+ final long sTime2 = DAYS.toNanos(3) + HOURS.toNanos(9) + MINUTES.toNanos(24) + SECONDS.toNanos(18) + MILLISECONDS.toNanos(572) + MICROSECONDS.toNanos(324) + NANOSECONDS.toNanos(823);
+
+ final long eTime3 = DAYS.toNanos(4) + HOURS.toNanos(19) + MINUTES.toNanos(55) + SECONDS.toNanos(23) + MILLISECONDS.toNanos(777) + MICROSECONDS.toNanos(0) + NANOSECONDS.toNanos(982);
+ String ds3 = nsSource.elapsedDisplayString0(sTime2, eTime3);
+ Assert.assertEquals("1d10h31m5s204ms676µs159ns", ds3);
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/util/TestFastList.java b/src/test/java/com/zaxxer/hikari/util/TestFastList.java
new file mode 100644
index 0000000..089ff5a
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/util/TestFastList.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.zaxxer.hikari.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.junit.Test;
+
+import com.zaxxer.hikari.mocks.StubStatement;
+
+public class TestFastList
+{
+ @Test
+ public void testAddRemove()
+ {
+ ArrayList<Statement> verifyList = new ArrayList<>();
+
+ FastList<Statement> list = new FastList<>(Statement.class);
+ for (int i = 0; i < 32; i++)
+ {
+ StubStatement statement = new StubStatement(null);
+ list.add(statement);
+ verifyList.add(statement);
+ }
+
+ for (int i = 0; i < 32; i++)
+ {
+ assertNotNull("Element " + i + " was null but should be " + verifyList.get(i), list.get(0));
+ int size = list.size();
+ list.remove(verifyList.get(i));
+ assertSame(size - 1, list.size());
+ }
+ }
+
+ @Test
+ public void testAddRemoveTail()
+ {
+ ArrayList<Statement> verifyList = new ArrayList<>();
+
+ FastList<Statement> list = new FastList<>(Statement.class);
+ for (int i = 0; i < 32; i++)
+ {
+ StubStatement statement = new StubStatement(null);
+ list.add(statement);
+ verifyList.add(statement);
+ }
+
+ for (int i = 31; i >= 0; i--)
+ {
+ assertNotNull("Element " + i, list.get(i));
+ int size = list.size();
+ list.remove(verifyList.get(i));
+ assertSame(size - 1, list.size());
+ }
+ }
+
+ @Test
+ public void testOverflow()
+ {
+ ArrayList<Statement> verifyList = new ArrayList<>();
+
+ FastList<Statement> list = new FastList<>(Statement.class);
+ for (int i = 0; i < 100; i++)
+ {
+ StubStatement statement = new StubStatement(null);
+ list.add(statement);
+ verifyList.add(statement);
+ }
+
+ for (int i = 0; i < 100; i++)
+ {
+ assertNotNull("Element " + i, list.get(i));
+ assertSame(verifyList.get(i), list.get(i));
+ }
+ }
+
+ @Test
+ public void testIterator()
+ {
+ FastList<Statement> list = new FastList<>(Statement.class);
+ for (int i = 0; i < 100; i++)
+ {
+ StubStatement statement = new StubStatement(null);
+ list.add(statement);
+ }
+
+ Iterator<Statement> iter = list.iterator();
+ for (int i = 0; i < list.size(); i++) {
+ assertSame(list.get(i), iter.next());
+ }
+ }
+
+ @Test
+ public void testClear()
+ {
+ FastList<Statement> list = new FastList<>(Statement.class);
+ for (int i = 0; i < 100; i++)
+ {
+ StubStatement statement = new StubStatement(null);
+ list.add(statement);
+ }
+
+ assertNotEquals(0, list.size());
+ list.clear();
+ assertEquals(0, list.size());
+ }
+
+ @Test
+ public void testRemoveLast()
+ {
+ FastList<Statement> list = new FastList<>(Statement.class);
+
+ Statement last = null;
+ for (int i = 0; i < 100; i++)
+ {
+ StubStatement statement = new StubStatement(null);
+ list.add(statement);
+ last = statement;
+ }
+
+ assertEquals(last, list.removeLast());
+ assertEquals(99, list.size());
+ }
+
+ @Test
+ public void testPolyMorphism1()
+ {
+ class Foo implements Base2 {
+
+ }
+
+ class Bar extends Foo {
+
+ }
+
+ FastList<Base> list = new FastList<>(Base.class, 2);
+ list.add(new Foo());
+ list.add(new Foo());
+ list.add(new Bar());
+ }
+
+ interface Base
+ {
+
+ }
+
+ interface Base2 extends Base
+ {
+
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java b/src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java
new file mode 100644
index 0000000..06957b2
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2017 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.util;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.lang.ref.Reference;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.concurrent.CompletableFuture;
+
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry;
+
+/**
+ * @author Brett Wooldridge
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class TomcatConcurrentBagLeakTest
+{
+ @Test
+ public void testConcurrentBagForLeaks() throws Exception
+ {
+ ClassLoader cl = new FauxWebClassLoader();
+ Class<?> clazz = cl.loadClass(this.getClass().getName() + "$FauxWebContext");
+ Object fauxWebContext = clazz.newInstance();
+
+ Method createConcurrentBag = clazz.getDeclaredMethod("createConcurrentBag");
+ createConcurrentBag.invoke(fauxWebContext);
+
+ Field failureException = clazz.getDeclaredField("failureException");
+ Exception ex = (Exception) failureException.get(fauxWebContext);
+ assertNull(ex);
+ }
+
+ @Test
+ public void testConcurrentBagForLeaks2() throws Exception
+ {
+ ClassLoader cl = this.getClass().getClassLoader();
+ Class<?> clazz = cl.loadClass(this.getClass().getName() + "$FauxWebContext");
+ Object fauxWebContext = clazz.newInstance();
+
+ Method createConcurrentBag = clazz.getDeclaredMethod("createConcurrentBag");
+ createConcurrentBag.invoke(fauxWebContext);
+
+ Field failureException = clazz.getDeclaredField("failureException");
+ Exception ex = (Exception) failureException.get(fauxWebContext);
+ assertNotNull(ex);
+ }
+
+ static class FauxWebClassLoader extends ClassLoader
+ {
+ static final byte[] classBytes = new byte[16_000];
+
+ @Override
+ public Class<?> loadClass(String name) throws ClassNotFoundException
+ {
+ if (name.startsWith("java") || name.startsWith("org")) {
+ return super.loadClass(name, true);
+ }
+
+ final String resourceName = "/" + name.replace('.', '/') + ".class";
+ final URL resource = this.getClass().getResource(resourceName);
+ try (DataInputStream is = new DataInputStream(resource.openStream())) {
+ int read = 0;
+ while (read < classBytes.length) {
+ final int rc = is.read(classBytes, read, classBytes.length - read);
+ if (rc == -1) {
+ break;
+ }
+ read += rc;
+ }
+
+ return defineClass(name, classBytes, 0, read);
+ }
+ catch (IOException e) {
+ throw new ClassNotFoundException(name);
+ }
+ }
+ }
+
+ public static class PoolEntry implements IConcurrentBagEntry
+ {
+ private int state;
+
+ @Override
+ public boolean compareAndSet(int expectState, int newState)
+ {
+ this.state = newState;
+ return true;
+ }
+
+ @Override
+ public void setState(int newState)
+ {
+ this.state = newState;
+ }
+
+ @Override
+ public int getState()
+ {
+ return state;
+ }
+ }
+
+ public static class FauxWebContext
+ {
+ private static final Logger log = LoggerFactory.getLogger(FauxWebContext.class);
+
+ public Exception failureException;
+
+ public void createConcurrentBag() throws InterruptedException
+ {
+ try (ConcurrentBag<PoolEntry> bag = new ConcurrentBag<>((x) -> CompletableFuture.completedFuture(Boolean.TRUE))) {
+
+ PoolEntry entry = new PoolEntry();
+ bag.add(entry);
+
+ PoolEntry borrowed = bag.borrow(100, MILLISECONDS);
+ bag.requite(borrowed);
+
+ PoolEntry removed = bag.borrow(100, MILLISECONDS);
+ bag.remove(removed);
+ }
+
+ checkThreadLocalsForLeaks();
+ }
+
+ private void checkThreadLocalsForLeaks()
+ {
+ Thread[] threads = getThreads();
+
+ try {
+ // Make the fields in the Thread class that store ThreadLocals
+ // accessible
+ Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
+ threadLocalsField.setAccessible(true);
+ Field inheritableThreadLocalsField = Thread.class.getDeclaredField("inheritableThreadLocals");
+ inheritableThreadLocalsField.setAccessible(true);
+ // Make the underlying array of ThreadLoad.ThreadLocalMap.Entry objects
+ // accessible
+ Class<?> tlmClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
+ Field tableField = tlmClass.getDeclaredField("table");
+ tableField.setAccessible(true);
+ Method expungeStaleEntriesMethod = tlmClass.getDeclaredMethod("expungeStaleEntries");
+ expungeStaleEntriesMethod.setAccessible(true);
+
+ for (int i = 0; i < threads.length; i++) {
+ Object threadLocalMap;
+ if (threads[i] != null) {
+
+ // Clear the first map
+ threadLocalMap = threadLocalsField.get(threads[i]);
+ if (null != threadLocalMap) {
+ expungeStaleEntriesMethod.invoke(threadLocalMap);
+ checkThreadLocalMapForLeaks(threadLocalMap, tableField);
+ }
+
+ // Clear the second map
+ threadLocalMap = inheritableThreadLocalsField.get(threads[i]);
+ if (null != threadLocalMap) {
+ expungeStaleEntriesMethod.invoke(threadLocalMap);
+ checkThreadLocalMapForLeaks(threadLocalMap, tableField);
+ }
+ }
+ }
+ }
+ catch (Throwable t) {
+ log.warn("Failed to check for ThreadLocal references for web application [{}]", t);
+ failureException = new Exception();
+ }
+ }
+
+ private Object getContextName()
+ {
+ return this.getClass().getName();
+ }
+
+ // THE FOLLOWING CODE COPIED FROM APACHE TOMCAT (2017/01/08)
+
+ /**
+ * Analyzes the given thread local map object. Also pass in the field that
+ * points to the internal table to save re-calculating it on every
+ * call to this method.
+ */
+ private void checkThreadLocalMapForLeaks(Object map, Field internalTableField) throws IllegalAccessException, NoSuchFieldException
+ {
+ if (map != null) {
+ Object[] table = (Object[]) internalTableField.get(map);
+ if (table != null) {
+ for (int j = 0; j < table.length; j++) {
+ Object obj = table[j];
+ if (obj != null) {
+ boolean keyLoadedByWebapp = false;
+ boolean valueLoadedByWebapp = false;
+ // Check the key
+ Object key = ((Reference<?>) obj).get();
+ if (this.equals(key) || loadedByThisOrChild(key)) {
+ keyLoadedByWebapp = true;
+ }
+ // Check the value
+ Field valueField = obj.getClass().getDeclaredField("value");
+ valueField.setAccessible(true);
+ Object value = valueField.get(obj);
+ if (this.equals(value) || loadedByThisOrChild(value)) {
+ valueLoadedByWebapp = true;
+ }
+ if (keyLoadedByWebapp || valueLoadedByWebapp) {
+ Object[] args = new Object[5];
+ args[0] = getContextName();
+ if (key != null) {
+ args[1] = getPrettyClassName(key.getClass());
+ try {
+ args[2] = key.toString();
+ }
+ catch (Exception e) {
+ log.warn("Unable to determine string representation of key of type [{}]", args[1], e);
+ args[2] = "Unknown";
+ }
+ }
+ if (value != null) {
+ args[3] = getPrettyClassName(value.getClass());
+ try {
+ args[4] = value.toString();
+ }
+ catch (Exception e) {
+ log.warn("webappClassLoader.checkThreadLocalsForLeaks.badValue {}", args[3], e);
+ args[4] = "Unknown";
+ }
+ }
+
+ if (valueLoadedByWebapp) {
+ log.error("The web application [{}] created a ThreadLocal with key " +
+ "(value [{}]) and a value of type [{}] (value [{}]) but failed to remove " +
+ "it when the web application was stopped. Threads are going to be renewed " +
+ "over time to try and avoid a probable memory leak.", args);
+ failureException = new Exception();
+ }
+ else if (value == null) {
+ log.debug("The web application [{}] created a ThreadLocal with key of type [{}] " +
+ "(value [{}]). The ThreadLocal has been correctly set to null and the " +
+ "key will be removed by GC.", args);
+ failureException = new Exception();
+ }
+ else {
+ log.debug("The web application [{}] created a ThreadLocal with key of type [{}] " +
+ "(value [{}]) and a value of type [{}] (value [{}]). Since keys are only " +
+ "weakly held by the ThreadLocal Map this is not a memory leak.", args);
+ failureException = new Exception();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private boolean loadedByThisOrChild(Object key)
+ {
+ return key.getClass().getClassLoader() == this.getClass().getClassLoader();
+ }
+
+ /*
+ * Get the set of current threads as an array.
+ */
+ private Thread[] getThreads()
+ {
+ // Get the current thread group
+ ThreadGroup tg = Thread.currentThread().getThreadGroup();
+ // Find the root thread group
+ try {
+ while (tg.getParent() != null) {
+ tg = tg.getParent();
+ }
+ }
+ catch (SecurityException se) {
+ log.warn("Unable to obtain the parent for ThreadGroup [{}]. It will not be possible to check all threads for potential memory leaks", tg.getName(), se);
+ }
+
+ int threadCountGuess = tg.activeCount() + 50;
+ Thread[] threads = new Thread[threadCountGuess];
+ int threadCountActual = tg.enumerate(threads);
+ // Make sure we don't miss any threads
+ while (threadCountActual == threadCountGuess) {
+ threadCountGuess *= 2;
+ threads = new Thread[threadCountGuess];
+ // Note tg.enumerate(Thread[]) silently ignores any threads that
+ // can't fit into the array
+ threadCountActual = tg.enumerate(threads);
+ }
+
+ return threads;
+ }
+
+ private String getPrettyClassName(Class<?> clazz)
+ {
+ String name = clazz.getCanonicalName();
+ if (name == null) {
+ name = clazz.getName();
+ }
+ return name;
+ }
+ }
+}
diff --git a/src/test/resources/hibernate.properties b/src/test/resources/hibernate.properties
new file mode 100644
index 0000000..6d7ebd9
--- /dev/null
+++ b/src/test/resources/hibernate.properties
@@ -0,0 +1,4 @@
+hibernate.hikari.minimumIdle=5
+hibernate.hikari.connectionTestQuery=SELECT 1
+hibernate.hikari.dataSourceClassName=com.zaxxer.hikari.mocks.StubDataSource
+hibernate.connection.autocommit=false
diff --git a/src/test/resources/log4j2-test.xml b/src/test/resources/log4j2-test.xml
new file mode 100644
index 0000000..c316dfa
--- /dev/null
+++ b/src/test/resources/log4j2-test.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration status="OFF">
+ <appenders>
+ <Console name="Console" target="SYSTEM_ERR">
+ <PatternLayout pattern="%6r [seq%5sn] [%-26.26t] %-5level %-20c{1} - %msg%n" />
+ </Console>
+ </appenders>
+ <Loggers>
+ <Root level="info">
+ <AppenderRef ref="Console" />
+ </Root>
+ </Loggers>
+</configuration>
diff --git a/src/test/resources/propfile1.properties b/src/test/resources/propfile1.properties
new file mode 100644
index 0000000..bf00bdd
--- /dev/null
+++ b/src/test/resources/propfile1.properties
@@ -0,0 +1,4 @@
+minimumIdle=5
+connectionTestQuery=SELECT 1
+autoCommit=false
+dataSourceClassName=com.zaxxer.hikari.mocks.StubDataSource
diff --git a/src/test/resources/propfile2.properties b/src/test/resources/propfile2.properties
new file mode 100644
index 0000000..94c7aa4
--- /dev/null
+++ b/src/test/resources/propfile2.properties
@@ -0,0 +1,5 @@
+connectionTestQuery=SELECT 1
+autoCommit=false
+dataSourceClassName=com.zaxxer.hikari.mocks.StubDataSource
+dataSource.user=test
+dataSource.password=test \ No newline at end of file
diff --git a/src/test/resources/propfile3.properties b/src/test/resources/propfile3.properties
new file mode 100644
index 0000000..10fdcf1
--- /dev/null
+++ b/src/test/resources/propfile3.properties
@@ -0,0 +1,6 @@
+connectionTestQuery=SELECT 1
+autoCommit=false
+dataSourceClassName=com.zaxxer.hikari.mocks.StubDataSource
+dataSource.user=test
+dataSource.password=test
+dataSource.url=jdbc:stub:foo