summaryrefslogtreecommitdiff
path: root/src/main/java/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com')
-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
37 files changed, 7455 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;
+ }
+ }
+}