summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorApollon Oikonomopoulos <apoikos@debian.org>2017-09-13 00:26:37 +0300
committerApollon Oikonomopoulos <apoikos@debian.org>2017-09-13 00:26:37 +0300
commit808d040ea9d760bf468621984a3f2de865d35e7c (patch)
tree520db44f3458bcb02a60baacff82408fcfb49840 /src
parentbd7b6679cea5620446718911de7a6764f81a9a7a (diff)
New upstream version 2.7.1
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/zaxxer/hikari/HikariConfig.java161
-rw-r--r--src/main/java/com/zaxxer/hikari/HikariDataSource.java30
-rw-r--r--src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTracker.java115
-rw-r--r--src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerFactory.java22
-rw-r--r--src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java48
-rw-r--r--src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java94
-rw-r--r--src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java25
-rwxr-xr-xsrc/main/java/com/zaxxer/hikari/pool/HikariPool.java115
-rwxr-xr-xsrc/main/java/com/zaxxer/hikari/pool/PoolBase.java84
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/PoolEntry.java31
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java88
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyFactory.java15
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyLeakTask.java28
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyLeakTaskFactory.java54
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyPreparedStatement.java4
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyResultSet.java11
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyStatement.java22
-rwxr-xr-xsrc/main/java/com/zaxxer/hikari/util/ConcurrentBag.java32
-rw-r--r--src/main/java/com/zaxxer/hikari/util/DriverDataSource.java25
-rw-r--r--src/main/java/com/zaxxer/hikari/util/QueuedSequenceSynchronizer.java137
-rw-r--r--src/main/java/com/zaxxer/hikari/util/Sequence.java71
-rwxr-xr-xsrc/test/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerTest.java39
-rw-r--r--src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java11
-rw-r--r--src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java44
-rw-r--r--src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java2
-rw-r--r--src/test/java/com/zaxxer/hikari/mocks/StubStatement.java11
-rw-r--r--src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java118
-rwxr-xr-xsrc/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java4
-rwxr-xr-xsrc/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java2
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java2
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/MetricsTrackerTest.java86
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/MiscTest.java7
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/SaturatedPoolTest830.java156
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java5
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestElf.java23
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestJNDI.java27
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestMBean.java93
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestMetrics.java46
-rw-r--r--src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java112
39 files changed, 1310 insertions, 690 deletions
diff --git a/src/main/java/com/zaxxer/hikari/HikariConfig.java b/src/main/java/com/zaxxer/hikari/HikariConfig.java
index ef9f9be..f69f82e 100644
--- a/src/main/java/com/zaxxer/hikari/HikariConfig.java
+++ b/src/main/java/com/zaxxer/hikari/HikariConfig.java
@@ -16,10 +16,15 @@
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 com.codahale.metrics.health.HealthCheckRegistry;
+import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
+import com.zaxxer.hikari.util.PropertyElf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.sql.DataSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -33,18 +38,11 @@ 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;
+import static com.zaxxer.hikari.util.UtilityElf.getNullIfEmpty;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.concurrent.TimeUnit.SECONDS;
+@SuppressWarnings({"SameParameterValue", "unused"})
public class HikariConfig implements HikariConfigMXBean
{
private static final Logger LOGGER = LoggerFactory.getLogger(HikariConfig.class);
@@ -55,7 +53,7 @@ public class HikariConfig implements HikariConfigMXBean
private static final long MAX_LIFETIME = MINUTES.toMillis(30);
private static final int DEFAULT_POOL_SIZE = 10;
- private static boolean unitTest;
+ private static boolean unitTest = false;
// Properties changeable at runtime through the MBean
//
@@ -79,6 +77,7 @@ public class HikariConfig implements HikariConfigMXBean
private String jdbcUrl;
private String password;
private String poolName;
+ private String schema;
private String transactionIsolationName;
private String username;
private boolean isAutoCommit;
@@ -176,8 +175,7 @@ public class HikariConfig implements HikariConfigMXBean
/**
* 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)}.
+ * be more efficient on some databases and is recommended.
*
* @param connectionTestQuery a SQL query string
*/
@@ -313,13 +311,32 @@ public class HikariConfig implements HikariConfigMXBean
public void setDriverClassName(String driverClassName)
{
+ Class<?> driverClass = null;
+ try {
+ driverClass = this.getClass().getClassLoader().loadClass(driverClassName);
+ LOGGER.debug("Driver class found in the HikariConfig class classloader {}", this.getClass().getClassLoader());
+ } catch (ClassNotFoundException e) {
+ ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader();
+ if (threadContextClassLoader != null && threadContextClassLoader != this.getClass().getClassLoader()) {
+ try {
+ driverClass = threadContextClassLoader.loadClass(driverClassName);
+ LOGGER.debug("Driver class found in Thread context class loader {}", threadContextClassLoader);
+ } catch (ClassNotFoundException e1) {
+ LOGGER.error("Failed to load class of driverClassName {} in either of HikariConfig class classloader {} or Thread context classloader {}", driverClassName, this.getClass().getClassLoader(), threadContextClassLoader);
+ }
+ } else {
+ LOGGER.error("Failed to load class of driverClassName {} in HikariConfig class classloader {}", driverClassName, this.getClass().getClassLoader());
+ }
+ }
+ if (driverClass == null) {
+ throw new RuntimeException("Failed to load class of driverClassName [" + driverClassName + "] in either of HikariConfig class loader or Thread context classloader");
+ }
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);
+ throw new RuntimeException("Failed to instantiate class " + driverClassName, e);
}
}
@@ -410,23 +427,32 @@ public class HikariConfig implements HikariConfigMXBean
* 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.
+ * a {@code PoolInitializationException} will be thrown. </li>
+ * <li>A value of zero will <i>not</i> prevent the pool from starting in the
+ * case that a connection cannot be obtained. However, upon start the pool will
+ * attempt to obtain a connection and validate that the {@code connectionTestQuery}
+ * and {@code connectionInitSql} are valid. If those validations fail, an exception
+ * will be thrown. If a connection cannot be obtained, the validation is skipped
+ * and the 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>A value less than zero will <i>not</i> bypass any connection attempt and
+ * validation during startup, and therefore the pool will start immediately. The
+ * pool will continue to try to obtain connections in the background. This can mean
+ * that callers to {@code DataSource#getConnection()} may encounter exceptions. </li>
* </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.
- *
+ * Note that if this timeout value is greater than or equal to zero (0), and therefore an
+ * initial connection validation is performed, 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.
+ * pool initialization fails, or 0 to validate connection setup but continue with
+ * pool start, or less than zero to skip all initialization checks and start the
+ * pool without delay.
*/
public void setInitializationFailTimeout(long initializationFailTimeout)
{
@@ -457,8 +483,8 @@ public class HikariConfig implements HikariConfigMXBean
public void setInitializationFailFast(boolean failFast)
{
LOGGER.warn("The initializationFailFast propery is deprecated, see initializationFailTimeout");
-
- initializationFailTimeout = (failFast ? 1 : 0);
+
+ initializationFailTimeout = (failFast ? 1 : -1);
}
public boolean isIsolateInternalQueries()
@@ -519,24 +545,31 @@ public class HikariConfig implements HikariConfigMXBean
}
if (metricRegistry != null) {
- if (metricRegistry instanceof String) {
- try {
- InitialContext initCtx = new InitialContext();
- metricRegistry = initCtx.lookup((String) metricRegistry);
- }
- catch (NamingException e) {
- throw new IllegalArgumentException(e);
- }
- }
+ metricRegistry = getObjectOrPerformJndiLookup(metricRegistry);
- if (!(metricRegistry instanceof MetricRegistry)) {
- throw new IllegalArgumentException("Class must be an instance of com.codahale.metrics.MetricRegistry");
+ if (!(metricRegistry.getClass().getName().contains("MetricRegistry"))
+ && !(metricRegistry.getClass().getName().contains("MeterRegistry"))) {
+ throw new IllegalArgumentException("Class must be instance of com.codahale.metrics.MetricRegistry or io.micrometer.core.instrument.MeterRegistry");
}
}
this.metricRegistry = metricRegistry;
}
+ private Object getObjectOrPerformJndiLookup(Object object)
+ {
+ if (object instanceof String) {
+ try {
+ InitialContext initCtx = new InitialContext();
+ return initCtx.lookup((String) object);
+ }
+ catch (NamingException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+ return object;
+ }
+
/**
* Get the Codahale HealthCheckRegistry, could be null.
*
@@ -555,15 +588,7 @@ public class HikariConfig implements HikariConfigMXBean
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);
- }
- }
+ healthCheckRegistry = getObjectOrPerformJndiLookup(healthCheckRegistry);
if (!(healthCheckRegistry instanceof HealthCheckRegistry)) {
throw new IllegalArgumentException("Class must be an instance of com.codahale.metrics.health.HealthCheckRegistry");
@@ -748,13 +773,29 @@ public class HikariConfig implements HikariConfigMXBean
{
this.scheduledExecutor = executor;
}
-
+
public String getTransactionIsolation()
{
return transactionIsolationName;
}
/**
+ * Get the default schema name to be set on connections.
+ *
+ * @return the default schema name
+ */
+ public String getSchema() {
+ return schema;
+ }
+
+ /**
+ * Set the default schema name to be set on connections.
+ */
+ public void setSchema(String schema) {
+ this.schema = schema;
+ }
+
+ /**
* 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>.
@@ -807,6 +848,7 @@ public class HikariConfig implements HikariConfigMXBean
this.threadFactory = threadFactory;
}
+ @SuppressWarnings("StatementWithEmptyBody")
public void validate()
{
if (poolName == null) {
@@ -843,7 +885,8 @@ public class HikariConfig implements HikariConfigMXBean
LOGGER.warn("{} - using dataSourceClassName and ignoring jdbcUrl.", poolName);
}
}
- else if (jdbcUrl != null) {
+ else if (jdbcUrl != null || dataSourceJndiName != null) {
+ // ok
}
else if (driverClassName != null) {
LOGGER.error("{} - jdbcUrl is required with driverClassName.", poolName);
@@ -904,6 +947,7 @@ public class HikariConfig implements HikariConfigMXBean
}
}
+ @SuppressWarnings("StatementWithEmptyBody")
private void logConfiguration()
{
LOGGER.debug("{} - configuration:", poolName);
@@ -926,6 +970,9 @@ public class HikariConfig implements HikariConfigMXBean
else if (prop.matches("scheduledExecutorService|threadFactory") && value == null) {
value = "internal";
}
+ else if (prop.contains("jdbcUrl") && value instanceof String) {
+ value = ((String)value).replaceAll("([?&;]password=)[^&#;]*(.*)", "$1<masked>$2");
+ }
else if (prop.contains("password")) {
value = "<masked>";
}
@@ -938,12 +985,12 @@ public class HikariConfig implements HikariConfigMXBean
LOGGER.debug((prop + "................................................").substring(0, 32) + value);
}
catch (Exception e) {
- continue;
+ // continue
}
}
}
- protected void loadProperties(String propertyFileName)
+ private void loadProperties(String propertyFileName)
{
final File propFile = new File(propertyFileName);
try (final InputStream is = propFile.isFile() ? new FileInputStream(propFile) : this.getClass().getResourceAsStream(propertyFileName)) {
diff --git a/src/main/java/com/zaxxer/hikari/HikariDataSource.java b/src/main/java/com/zaxxer/hikari/HikariDataSource.java
index 29b8953..bc0f16c 100644
--- a/src/main/java/com/zaxxer/hikari/HikariDataSource.java
+++ b/src/main/java/com/zaxxer/hikari/HikariDataSource.java
@@ -264,6 +264,28 @@ public class HikariDataSource extends HikariConfig implements DataSource, Closea
}
/**
+ * Get the {@code HikariPoolMXBean} for this HikariDataSource instance. If this method is called on
+ * a {@code HikariDataSource} that has been constructed without a {@code HikariConfig} instance,
+ * and before an initial call to {@code #getConnection()}, the return value will be {@code null}.
+ *
+ * @return the {@code HikariPoolMXBean} instance, or {@code null}.
+ */
+ public HikariPoolMXBean getHikariPoolMXBean()
+ {
+ return pool;
+ }
+
+ /**
+ * Get the {@code HikariConfigMXBean} for this HikariDataSource instance.
+ *
+ * @return the {@code HikariConfigMXBean} instance.
+ */
+ public HikariConfigMXBean getHikariConfigMXBean()
+ {
+ return this;
+ }
+
+ /**
* 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.
@@ -281,7 +303,11 @@ public class HikariDataSource extends HikariConfig implements DataSource, Closea
/**
* Suspend allocation of connections from the pool. All callers to <code>getConnection()</code>
* will block indefinitely until <code>resumePool()</code> is called.
+ *
+ * @deprecated Call the {@code HikariPoolMXBean#suspendPool()} method on the {@code HikariPoolMXBean}
+ * obtained by {@code #getHikariPoolMXBean()} or JMX lookup.
*/
+ @Deprecated
public void suspendPool()
{
HikariPool p;
@@ -292,7 +318,11 @@ public class HikariDataSource extends HikariConfig implements DataSource, Closea
/**
* Resume allocation of connections from the pool.
+ *
+ * @deprecated Call the {@code HikariPoolMXBean#resumePool()} method on the {@code HikariPoolMXBean}
+ * obtained by {@code #getHikariPoolMXBean()} or JMX lookup.
*/
+ @Deprecated
public void resumePool()
{
HikariPool p;
diff --git a/src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTracker.java b/src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTracker.java
new file mode 100644
index 0000000..a0839b1
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTracker.java
@@ -0,0 +1,115 @@
+package com.zaxxer.hikari.metrics.micrometer;
+
+import com.zaxxer.hikari.metrics.IMetricsTracker;
+import com.zaxxer.hikari.metrics.PoolStats;
+import io.micrometer.core.instrument.DistributionSummary;
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Timer;
+import io.micrometer.core.instrument.stats.quantile.WindowSketchQuantiles;
+
+import java.util.concurrent.TimeUnit;
+
+import static io.micrometer.core.instrument.stats.hist.CumulativeHistogram.buckets;
+import static io.micrometer.core.instrument.stats.hist.CumulativeHistogram.linear;
+
+public class MicrometerMetricsTracker implements IMetricsTracker
+{
+ 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";
+
+ private final Timer connectionObtainTimer;
+ private final DistributionSummary connectionTimeoutMeter;
+ private final DistributionSummary connectionUsage;
+ private final DistributionSummary connectionCreation;
+ @SuppressWarnings({"FieldCanBeLocal", "unused"})
+ private final Gauge totalConnectionGauge;
+ @SuppressWarnings({"FieldCanBeLocal", "unused"})
+ private final Gauge idleConnectionGauge;
+ @SuppressWarnings({"FieldCanBeLocal", "unused"})
+ private final Gauge activeConnectionGauge;
+ @SuppressWarnings({"FieldCanBeLocal", "unused"})
+ private final Gauge pendingConnectionGauge;
+
+ MicrometerMetricsTracker(final String poolName, final PoolStats poolStats, final MeterRegistry meterRegistry)
+ {
+ this.connectionObtainTimer = meterRegistry
+ .timerBuilder(METRIC_NAME_WAIT)
+ .tags(METRIC_CATEGORY, poolName)
+ .create();
+
+ this.connectionCreation = meterRegistry
+ .summaryBuilder(METRIC_NAME_CONNECT)
+ .tags(METRIC_CATEGORY, poolName)
+ .quantiles(WindowSketchQuantiles.quantiles(0.5, 0.95).create())
+ .histogram(buckets(linear(0, 10, 20), TimeUnit.MILLISECONDS))
+ .create();
+
+ this.connectionUsage = meterRegistry
+ .summaryBuilder(METRIC_NAME_USAGE)
+ .tags(METRIC_CATEGORY, poolName)
+ .quantiles(WindowSketchQuantiles.quantiles(0.5, 0.95).create())
+ .histogram(buckets(linear(0, 10, 20), TimeUnit.MILLISECONDS))
+ .create();
+
+ this.connectionTimeoutMeter = meterRegistry
+ .summaryBuilder(METRIC_NAME_TIMEOUT_RATE)
+ .tags(METRIC_CATEGORY, poolName)
+ .quantiles(WindowSketchQuantiles.quantiles(0.5, 0.95).create())
+ .histogram(buckets(linear(0, 10, 20), TimeUnit.MILLISECONDS))
+ .create();
+
+ this.totalConnectionGauge = meterRegistry
+ .gaugeBuilder(METRIC_NAME_TOTAL_CONNECTIONS, Integer.class, (i) -> poolStats.getTotalConnections())
+ .tags(METRIC_CATEGORY, poolName)
+ .create();
+
+ this.idleConnectionGauge = meterRegistry
+ .gaugeBuilder(METRIC_NAME_IDLE_CONNECTIONS, Integer.class, (i) -> poolStats.getIdleConnections())
+ .tags(METRIC_CATEGORY, poolName)
+ .create();
+
+ this.activeConnectionGauge = meterRegistry
+ .gaugeBuilder(METRIC_NAME_ACTIVE_CONNECTIONS, Integer.class, (i) -> poolStats.getActiveConnections())
+ .tags(METRIC_CATEGORY, poolName)
+ .create();
+
+ this.pendingConnectionGauge = meterRegistry
+ .gaugeBuilder(METRIC_NAME_PENDING_CONNECTIONS, Integer.class, (i) -> poolStats.getPendingThreads())
+ .tags(METRIC_CATEGORY, poolName)
+ .create();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void recordConnectionAcquiredNanos(final long elapsedAcquiredNanos)
+ {
+ connectionObtainTimer.record(elapsedAcquiredNanos, TimeUnit.NANOSECONDS);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void recordConnectionUsageMillis(final long elapsedBorrowedMillis)
+ {
+ connectionUsage.record(elapsedBorrowedMillis);
+ }
+
+ @Override
+ public void recordConnectionTimeout()
+ {
+ connectionTimeoutMeter.count();
+ }
+
+ @Override
+ public void recordConnectionCreatedMillis(long connectionCreatedMillis)
+ {
+ connectionCreation.record(connectionCreatedMillis);
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerFactory.java b/src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerFactory.java
new file mode 100644
index 0000000..4072927
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerFactory.java
@@ -0,0 +1,22 @@
+package com.zaxxer.hikari.metrics.micrometer;
+
+import com.zaxxer.hikari.metrics.IMetricsTracker;
+import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
+import com.zaxxer.hikari.metrics.PoolStats;
+import io.micrometer.core.instrument.MeterRegistry;
+
+public class MicrometerMetricsTrackerFactory implements MetricsTrackerFactory {
+
+ private final MeterRegistry registry;
+
+ public MicrometerMetricsTrackerFactory(MeterRegistry registry)
+ {
+ this.registry = registry;
+ }
+
+ @Override
+ public IMetricsTracker create(String poolName, PoolStats poolStats)
+ {
+ return new MicrometerMetricsTracker(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
index 3bff974..3d5fcf0 100644
--- a/src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java
+++ b/src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java
@@ -18,38 +18,46 @@ package com.zaxxer.hikari.metrics.prometheus;
import com.zaxxer.hikari.metrics.PoolStats;
import io.prometheus.client.Collector;
-
+import io.prometheus.client.GaugeMetricFamily;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
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);
- }
+
+ private static final List<String> LABEL_NAMES = Collections.singletonList("pool");
+
+ private final Map<String, PoolStats> poolStatsMap = new ConcurrentHashMap<>();
@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())
+ createGauge("hikaricp_active_connections", "Active connections",
+ PoolStats::getActiveConnections),
+ createGauge("hikaricp_idle_connections", "Idle connections",
+ PoolStats::getIdleConnections),
+ createGauge("hikaricp_pending_threads", "Pending threads",
+ PoolStats::getPendingThreads),
+ createGauge("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)
- );
+ protected HikariCPCollector add(String name, PoolStats poolStats) {
+ poolStatsMap.put(name, poolStats);
+ return this;
+ }
- return new MetricFamilySamples(name, Type.GAUGE, helpMessage, samples);
+ private GaugeMetricFamily createGauge(String metric, String help,
+ Function<PoolStats, Integer> metricValueFunction) {
+ GaugeMetricFamily metricFamily = new GaugeMetricFamily(metric, help, LABEL_NAMES);
+ poolStatsMap.forEach((k, v) -> metricFamily.addMetric(
+ Collections.singletonList(k),
+ metricValueFunction.apply(v)
+ ));
+ return metricFamily;
}
}
diff --git a/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java
index 0bd54fc..ae856a5 100644
--- a/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java
+++ b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java
@@ -17,90 +17,68 @@
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;
+import java.util.concurrent.TimeUnit;
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);
+ private static final Counter CONNECTION_TIMEOUT_COUNTER = Counter.build()
+ .name("hikaricp_connection_timeout_total")
+ .labelNames("pool")
+ .help("Connection timeout total count")
+ .register();
+ private static final Summary ELAPSED_ACQUIRED_SUMMARY = Summary.build()
+ .name("hikaricp_connection_acquired_nanos")
+ .labelNames("pool")
+ .help("Connection acquired time (ns)")
+ .register();
+ private static final Summary ELAPSED_BORROWED_SUMMARY = Summary.build()
+ .name("hikaricp_connection_usage_millis")
+ .labelNames("pool")
+ .help("Connection usage (ms)")
+ .register();
+ private static final Summary ELAPSED_CREATION_SUMMARY = Summary.build()
+ .name("hikaricp_connection_creation_millis")
+ .labelNames("pool")
+ .help("Connection creation (ms)")
+ .register();
+
+ private final Counter.Child connectionTimeoutCounterChild;
+ private final Summary.Child elapsedAcquiredSummaryChild;
+ private final Summary.Child elapsedBorrowedSummaryChild;
+ private final Summary.Child elapsedCreationSummaryChild;
+
+ PrometheusMetricsTracker(String poolName) {
+ this.connectionTimeoutCounterChild = CONNECTION_TIMEOUT_COUNTER.labels(poolName);
+ this.elapsedAcquiredSummaryChild = ELAPSED_ACQUIRED_SUMMARY.labels(poolName);
+ this.elapsedBorrowedSummaryChild = ELAPSED_BORROWED_SUMMARY.labels(poolName);
+ this.elapsedCreationSummaryChild = ELAPSED_CREATION_SUMMARY.labels(poolName);
}
@Override
public void recordConnectionAcquiredNanos(long elapsedAcquiredNanos)
{
- elapsedAcquiredSummary.observe(elapsedAcquiredNanos);
+ elapsedAcquiredSummaryChild.observe(elapsedAcquiredNanos);
}
@Override
public void recordConnectionUsageMillis(long elapsedBorrowedMillis)
{
- elapsedBorrowedSummary.observe(elapsedBorrowedMillis);
+ elapsedBorrowedSummaryChild.observe(elapsedBorrowedMillis);
}
@Override
public void recordConnectionCreatedMillis(long connectionCreatedMillis)
{
- elapsedCreationSummary.observe(connectionCreatedMillis);
+ elapsedCreationSummaryChild.observe(connectionCreatedMillis);
}
@Override
public void recordConnectionTimeout()
{
- connectionTimeoutCounter.inc();
+ connectionTimeoutCounterChild.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
index 2a0b5c3..0d18a35 100644
--- a/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java
+++ b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java
@@ -20,20 +20,29 @@ 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
-{
+public class PrometheusMetricsTrackerFactory implements MetricsTrackerFactory {
+
+ private static HikariCPCollector collector;
+
@Override
- public IMetricsTracker create(String poolName, PoolStats poolStats)
- {
- Collector collector = new HikariCPCollector(poolName, poolStats).register();
- return new PrometheusMetricsTracker(poolName, collector);
+ public IMetricsTracker create(String poolName, PoolStats poolStats) {
+ getCollector().add(poolName, poolStats);
+ return new PrometheusMetricsTracker(poolName);
+ }
+
+ /**
+ * initialize and register collector if it isn't initialized yet
+ */
+ private HikariCPCollector getCollector() {
+ if (collector == null) {
+ collector = new HikariCPCollector().register();
+ }
+ return collector;
}
}
diff --git a/src/main/java/com/zaxxer/hikari/pool/HikariPool.java b/src/main/java/com/zaxxer/hikari/pool/HikariPool.java
index e9b7364..94e4e43 100755
--- a/src/main/java/com/zaxxer/hikari/pool/HikariPool.java
+++ b/src/main/java/com/zaxxer/hikari/pool/HikariPool.java
@@ -16,11 +16,6 @@
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;
@@ -29,17 +24,19 @@ import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_IN_
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 static java.util.Collections.unmodifiableCollection;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
import java.sql.Connection;
import java.sql.SQLException;
-import java.sql.SQLTimeoutException;
import java.sql.SQLTransientConnectionException;
import java.util.Collection;
+import java.util.List;
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;
@@ -48,6 +45,8 @@ import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.ThreadPoolExecutor;
+import com.zaxxer.hikari.metrics.micrometer.MicrometerMetricsTrackerFactory;
+import io.micrometer.core.instrument.MeterRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -84,13 +83,14 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
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 PoolEntryCreator POST_FILL_POOL_ENTRY_CREATOR = new PoolEntryCreator("After adding ");
private final Collection<Runnable> addConnectionQueue;
private final ThreadPoolExecutor addConnectionExecutor;
private final ThreadPoolExecutor closeConnectionExecutor;
private final ConcurrentBag<PoolEntry> connectionBag;
- private final ProxyLeakTask leakTask;
+ private final ProxyLeakTaskFactory leakTaskFactory;
private final SuspendResumeLock suspendResumeLock;
private ScheduledExecutorService houseKeepingExecutorService;
@@ -108,7 +108,7 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
this.connectionBag = new ConcurrentBag<>(this);
this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
- initializeHouseKeepingExecutorService();
+ this.houseKeepingExecutorService = initializeHouseKeepingExecutorService();
checkFailFast();
@@ -130,9 +130,9 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
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.leakTaskFactory = new ProxyLeakTaskFactory(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
- this.houseKeeperTask = this.houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, HOUSEKEEPING_PERIOD_MS, MILLISECONDS);
+ this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, HOUSEKEEPING_PERIOD_MS, MILLISECONDS);
}
/**
@@ -175,9 +175,11 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
}
else {
metricsTracker.recordBorrowStats(poolEntry, startTime);
- return poolEntry.createProxyConnection(leakTask.schedule(poolEntry), now);
+ return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);
}
} while (timeout > 0L);
+
+ metricsTracker.recordBorrowTimeoutStats(startTime);
}
catch (InterruptedException e) {
if (poolEntry != null) {
@@ -219,7 +221,7 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
softEvictConnections();
addConnectionExecutor.shutdown();
- addConnectionExecutor.awaitTermination(5L, SECONDS);
+ addConnectionExecutor.awaitTermination(getLoginTimeout(), SECONDS);
destroyHouseKeepingExecutorService();
@@ -232,16 +234,16 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
do {
abortActiveConnections(assassinExecutor);
softEvictConnections();
- } while (getTotalConnections() > 0 && elapsedMillis(start) < SECONDS.toMillis(5));
+ } while (getTotalConnections() > 0 && elapsedMillis(start) < SECONDS.toMillis(10));
}
finally {
assassinExecutor.shutdown();
- assassinExecutor.awaitTermination(5L, SECONDS);
+ assassinExecutor.awaitTermination(10L, SECONDS);
}
shutdownNetworkTimeoutExecutor();
closeConnectionExecutor.shutdown();
- closeConnectionExecutor.awaitTermination(5L, SECONDS);
+ closeConnectionExecutor.awaitTermination(10L, SECONDS);
}
finally {
logPoolState("After shutdown ");
@@ -270,9 +272,12 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
public void setMetricRegistry(Object metricRegistry)
{
- if (metricRegistry != null) {
+ if (metricRegistry instanceof MetricRegistry) {
setMetricsTrackerFactory(new CodahaleMetricsTrackerFactory((MetricRegistry) metricRegistry));
}
+ else if (metricRegistry instanceof MeterRegistry) {
+ setMetricsTrackerFactory(new MicrometerMetricsTrackerFactory((MeterRegistry) metricRegistry));
+ }
else {
setMetricsTrackerFactory(null);
}
@@ -301,14 +306,14 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
/** {@inheritDoc} */
@Override
- public Future<Boolean> addBagItem(final int waiting)
+ public void addBagItem(final int waiting)
{
final boolean shouldAdd = waiting - addConnectionQueue.size() >= 0; // Yes, >= is intentional.
if (shouldAdd) {
- return addConnectionExecutor.submit(POOL_ENTRY_CREATOR);
+ addConnectionExecutor.submit(POOL_ENTRY_CREATOR);
}
- return CompletableFuture.completedFuture(Boolean.TRUE);
+ CompletableFuture.completedFuture(Boolean.TRUE);
}
// ***********************************************************************
@@ -424,6 +429,7 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
}
}
+ @SuppressWarnings("unused")
int[] getPoolStateCounts()
{
return connectionBag.getStateCounts();
@@ -447,7 +453,11 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
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 */),
+ () -> {
+ if (softEvictConnection(poolEntry, "(connection has passed maxLifetime)", false /* not owner */)) {
+ addBagItem(connectionBag.getWaitingThreadCount());
+ }
+ },
lifetime, MILLISECONDS));
}
@@ -469,7 +479,7 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
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 "));
+ addConnectionExecutor.submit((i < connectionsToAdd - 1) ? POOL_ENTRY_CREATOR : POST_FILL_POOL_ENTRY_CREATOR);
}
}
@@ -499,8 +509,12 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
*/
private void checkFailFast()
{
+ final long initializationTimeout = config.getInitializationFailTimeout();
+ if (initializationTimeout < 0) {
+ return;
+ }
+
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) {
@@ -509,23 +523,21 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
LOGGER.debug("{} - Added connection {}", poolName, poolEntry.connection);
}
else {
- final Connection connection = poolEntry.close();
- quietlyCloseConnection(connection, "(initialization check complete and minimumIdle is zero)");
+ quietlyCloseConnection(poolEntry.close(), "(initialization check complete and minimumIdle is zero)");
}
return;
}
- throwable = getLastConnectionFailure();
- if (throwable instanceof ConnectionSetupException) {
- throwPoolInitializationException(throwable.getCause());
+ if (getLastConnectionFailure() instanceof ConnectionSetupException) {
+ throwPoolInitializationException(getLastConnectionFailure().getCause());
}
quietlySleep(1000L);
- } while (elapsedMillis(startTime) < config.getInitializationFailTimeout());
+ } while (elapsedMillis(startTime) < initializationTimeout);
- if (config.getInitializationFailTimeout() > 0) {
- throwPoolInitializationException(throwable);
+ if (initializationTimeout > 0) {
+ throwPoolInitializationException(getLastConnectionFailure());
}
}
@@ -536,25 +548,28 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
throw new PoolInitializationException(t);
}
- private void softEvictConnection(final PoolEntry poolEntry, final String reason, final boolean owner)
+ private boolean softEvictConnection(final PoolEntry poolEntry, final String reason, final boolean owner)
{
poolEntry.markEvicted();
if (owner || connectionBag.reserve(poolEntry)) {
closeConnection(poolEntry, reason);
+ return true;
}
+
+ return false;
}
- private void initializeHouseKeepingExecutorService()
+ private ScheduledExecutorService 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;
+ return executor;
}
else {
- this.houseKeepingExecutorService = config.getScheduledExecutor();
+ return config.getScheduledExecutor();
}
}
@@ -605,11 +620,11 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
*/
private final class PoolEntryCreator implements Callable<Boolean>
{
- private final String afterPrefix;
+ private final String loggingPrefix;
- PoolEntryCreator(String afterPrefix)
+ PoolEntryCreator(String loggingPrefix)
{
- this.afterPrefix = afterPrefix;
+ this.loggingPrefix = loggingPrefix;
}
@Override
@@ -621,8 +636,8 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
if (poolEntry != null) {
connectionBag.add(poolEntry);
LOGGER.debug("{} - Added connection {}", poolName, poolEntry.connection);
- if (afterPrefix != null) {
- logPoolState(afterPrefix);
+ if (loggingPrefix != null) {
+ logPoolState(loggingPrefix);
}
return Boolean.TRUE;
}
@@ -657,7 +672,7 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
// refresh timeouts in case they changed via MBean
connectionTimeout = config.getConnectionTimeout();
validationTimeout = config.getValidationTimeout();
- leakTask.updateLeakDetectionThreshold(config.getLeakDetectionThreshold());
+ leakTaskFactory.updateLeakDetectionThreshold(config.getLeakDetectionThreshold());
final long idleTimeout = config.getIdleTimeout();
final long now = currentTime();
@@ -683,14 +698,16 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
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)"));
+ final List<PoolEntry> notInUse = connectionBag.values(STATE_NOT_IN_USE);
+ int removed = 0;
+ for (PoolEntry entry : notInUse) {
+ if (elapsedMillis(entry.lastAccessed, now) > idleTimeout && connectionBag.reserve(entry)) {
+ closeConnection(entry, "(connection has passed idleTimeout)");
+ if (++removed > config.getMinimumIdle()) {
+ break;
+ }
+ }
+ }
}
logPoolState(afterPrefix);
diff --git a/src/main/java/com/zaxxer/hikari/pool/PoolBase.java b/src/main/java/com/zaxxer/hikari/pool/PoolBase.java
index b463dae..9063d81 100755
--- a/src/main/java/com/zaxxer/hikari/pool/PoolBase.java
+++ b/src/main/java/com/zaxxer/hikari/pool/PoolBase.java
@@ -16,6 +16,7 @@
package com.zaxxer.hikari.pool;
+import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_SCHEMA;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -29,12 +30,16 @@ import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.management.MBeanServer;
import javax.management.ObjectName;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
import javax.sql.DataSource;
+import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -61,11 +66,11 @@ abstract class PoolBase
protected final HikariConfig config;
protected final String poolName;
- protected long connectionTimeout;
- protected long validationTimeout;
- protected IMetricsTrackerDelegate metricsTracker;
+ long connectionTimeout;
+ long validationTimeout;
+ IMetricsTrackerDelegate metricsTracker;
- private static final String[] RESET_STATES = {"readOnly", "autoCommit", "isolation", "catalog", "netTimeout"};
+ private static final String[] RESET_STATES = {"readOnly", "autoCommit", "isolation", "catalog", "netTimeout", "schema"};
private static final int UNINITIALIZED = -1;
private static final int TRUE = 1;
private static final int FALSE = 0;
@@ -79,6 +84,7 @@ abstract class PoolBase
private DataSource dataSource;
private final String catalog;
+ private final String schema;
private final boolean isReadOnly;
private final boolean isAutoCommit;
@@ -94,6 +100,7 @@ abstract class PoolBase
this.networkTimeout = UNINITIALIZED;
this.catalog = config.getCatalog();
+ this.schema = config.getSchema();
this.isReadOnly = config.isReadOnly();
this.isAutoCommit = config.isAutoCommit();
this.transactionIsolation = UtilityElf.getTransactionIsolation(config.getTransactionIsolation());
@@ -146,28 +153,30 @@ abstract class PoolBase
{
try {
try {
+ setNetworkTimeout(connection, validationTimeout);
+
+ final long validationSeconds = (int) Math.max(1000L, validationTimeout) / 1000;
+
if (isUseJdbc4Validation) {
- return connection.isValid((int) MILLISECONDS.toSeconds(Math.max(1000L, validationTimeout)));
+ return connection.isValid((int) validationSeconds);
}
- setNetworkTimeout(connection, validationTimeout);
-
try (Statement statement = connection.createStatement()) {
if (isNetworkTimeoutSupported != TRUE) {
- setQueryTimeout(statement, (int) MILLISECONDS.toSeconds(Math.max(1000L, validationTimeout)));
+ setQueryTimeout(statement, (int) validationSeconds);
}
statement.execute(config.getConnectionTestQuery());
}
}
finally {
+ setNetworkTimeout(connection, networkTimeout);
+
if (isIsolateInternalQueries && !isAutoCommit) {
connection.rollback();
}
}
- setNetworkTimeout(connection, networkTimeout);
-
return true;
}
catch (Exception e) {
@@ -225,6 +234,11 @@ abstract class PoolBase
resetBits |= DIRTY_BIT_NETTIMEOUT;
}
+ if ((dirtyBits & DIRTY_BIT_SCHEMA) != 0 && schema != null && !schema.equals(proxyConnection.getSchemaState())) {
+ connection.setSchema(schema);
+ resetBits |= DIRTY_BIT_SCHEMA;
+ }
+
if (resetBits != 0 && LOGGER.isDebugEnabled()) {
LOGGER.debug("{} - Reset ({}) on connection {}", poolName, stringFromResetBits(resetBits), connection);
}
@@ -237,6 +251,15 @@ abstract class PoolBase
}
}
+ long getLoginTimeout()
+ {
+ try {
+ return (dataSource != null) ? dataSource.getLoginTimeout() : SECONDS.toSeconds(5);
+ } catch (SQLException e) {
+ return SECONDS.toSeconds(5);
+ }
+ }
+
// ***********************************************************************
// JMX methods
// ***********************************************************************
@@ -244,7 +267,7 @@ abstract class PoolBase
/**
* Register MBeans for HikariConfig and HikariPool.
*
- * @param pool a HikariPool instance
+ * @param hikariPool a HikariPool instance
*/
void registerMBeans(final HikariPool hikariPool)
{
@@ -300,8 +323,6 @@ abstract class PoolBase
/**
* Create/initialize the underlying DataSource.
- *
- * @return a DataSource instance
*/
private void initializeDataSource()
{
@@ -310,6 +331,7 @@ abstract class PoolBase
final String password = config.getPassword();
final String dsClassName = config.getDataSourceClassName();
final String driverClassName = config.getDriverClassName();
+ final String dataSourceJNDI = config.getDataSourceJNDI();
final Properties dataSourceProperties = config.getDataSourceProperties();
DataSource dataSource = config.getDataSource();
@@ -320,6 +342,14 @@ abstract class PoolBase
else if (jdbcUrl != null && dataSource == null) {
dataSource = new DriverDataSource(jdbcUrl, driverClassName, dataSourceProperties, username, password);
}
+ else if (dataSourceJNDI != null && dataSource == null) {
+ try {
+ InitialContext ic = new InitialContext();
+ dataSource = (DataSource) ic.lookup(dataSourceJNDI);
+ } catch (NamingException e) {
+ throw new PoolInitializationException(e);
+ }
+ }
if (dataSource != null) {
setLoginTimeout(dataSource);
@@ -334,7 +364,7 @@ abstract class PoolBase
*
* @return a Connection connection
*/
- Connection newConnection() throws Exception
+ private Connection newConnection() throws Exception
{
final long start = currentTime();
@@ -375,7 +405,7 @@ abstract class PoolBase
* Setup a connection initial state.
*
* @param connection a Connection
- * @throws SQLException thrown from driver
+ * @throws ConnectionSetupException thrown if any exception is encountered
*/
private void setupConnection(final Connection connection) throws ConnectionSetupException
{
@@ -400,6 +430,10 @@ abstract class PoolBase
connection.setCatalog(catalog);
}
+ if (schema != null) {
+ connection.setSchema(schema);
+ }
+
executeSql(connection, config.getConnectionInitSql(), true);
setNetworkTimeout(connection, networkTimeout);
@@ -438,10 +472,12 @@ abstract class PoolBase
}
catch (SQLException e) {
LOGGER.warn("{} - Default transaction isolation level detection failed ({}).", poolName, e.getMessage());
+ if (e.getSQLState() != null && !e.getSQLState().startsWith("08")) {
+ throw e;
+ }
}
- finally {
- isValidChecked = true;
- }
+
+ isValidChecked = true;
}
}
@@ -610,7 +646,7 @@ abstract class PoolBase
{
private static final long serialVersionUID = 929872118275916521L;
- public ConnectionSetupException(Throwable t)
+ ConnectionSetupException(Throwable t)
{
super(t);
}
@@ -635,12 +671,14 @@ abstract class PoolBase
}
}
- static interface IMetricsTrackerDelegate extends AutoCloseable
+ interface IMetricsTrackerDelegate extends AutoCloseable
{
default void recordConnectionUsage(PoolEntry poolEntry) {}
default void recordConnectionCreated(long connectionCreatedMillis) {}
+ default void recordBorrowTimeoutStats(long startTime) {}
+
default void recordBorrowStats(final PoolEntry poolEntry, final long startTime) {}
default void recordConnectionTimeout() {}
@@ -676,6 +714,12 @@ abstract class PoolBase
}
@Override
+ public void recordBorrowTimeoutStats(long startTime)
+ {
+ tracker.recordConnectionAcquiredNanos(elapsedNanos(startTime));
+ }
+
+ @Override
public void recordBorrowStats(final PoolEntry poolEntry, final long startTime)
{
final long now = currentTime();
diff --git a/src/main/java/com/zaxxer/hikari/pool/PoolEntry.java b/src/main/java/com/zaxxer/hikari/pool/PoolEntry.java
index 5b16047..2b45256 100644
--- a/src/main/java/com/zaxxer/hikari/pool/PoolEntry.java
+++ b/src/main/java/com/zaxxer/hikari/pool/PoolEntry.java
@@ -15,22 +15,18 @@
*/
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 com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry;
+import com.zaxxer.hikari.util.FastList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
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;
+import static com.zaxxer.hikari.util.ClockSource.*;
/**
* Entry used in the ConcurrentBag to track Connection instances.
@@ -42,12 +38,12 @@ 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;
+
+ @SuppressWarnings("FieldCanBeLocal")
+ private volatile int state = 0;
private volatile boolean evict;
private volatile ScheduledFuture<?> endOfLife;
@@ -60,13 +56,6 @@ final class PoolEntry implements IConcurrentBagEntry
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");
}
@@ -94,7 +83,9 @@ final class PoolEntry implements IConcurrentBagEntry
}
/**
- * @param endOfLife
+ * Set the end of life {@link ScheduledFuture}.
+ *
+ * @param endOfLife this PoolEntry/Connection's end of life {@link ScheduledFuture}
*/
void setFutureEol(final ScheduledFuture<?> endOfLife)
{
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java b/src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java
index 7143bae..2d07e31 100644
--- a/src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java
@@ -19,10 +19,10 @@ 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.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Savepoint;
@@ -44,16 +44,18 @@ import com.zaxxer.hikari.util.FastList;
*/
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;
+ static final int DIRTY_BIT_READONLY = 0b000001;
+ static final int DIRTY_BIT_AUTOCOMMIT = 0b000010;
+ static final int DIRTY_BIT_ISOLATION = 0b000100;
+ static final int DIRTY_BIT_CATALOG = 0b001000;
+ static final int DIRTY_BIT_NETTIMEOUT = 0b010000;
+ static final int DIRTY_BIT_SCHEMA = 0b100000;
private static final Logger LOGGER;
private static final Set<String> ERROR_STATES;
private static final Set<Integer> ERROR_CODES;
+ @SuppressWarnings("WeakerAccess")
protected Connection delegate;
private final PoolEntry poolEntry;
@@ -69,6 +71,7 @@ public abstract class ProxyConnection implements Connection
private int networkTimeout;
private int transactionIsolation;
private String dbcatalog;
+ private String dbschema;
// static initializer
static {
@@ -101,10 +104,7 @@ public abstract class ProxyConnection implements Connection
@Override
public final String toString()
{
- return new StringBuilder(64)
- .append(this.getClass().getSimpleName()).append('@').append(System.identityHashCode(this))
- .append(" wrapping ")
- .append(delegate).toString();
+ return this.getClass().getSimpleName() + '@' + System.identityHashCode(this) + " wrapping " + delegate;
}
// ***********************************************************************
@@ -121,6 +121,11 @@ public abstract class ProxyConnection implements Connection
return dbcatalog;
}
+ final String getSchemaState()
+ {
+ return dbschema;
+ }
+
final int getTransactionIsolationState()
{
return transactionIsolation;
@@ -186,29 +191,32 @@ public abstract class ProxyConnection implements Connection
leakTask.cancel();
}
- private final synchronized <T extends Statement> T trackStatement(final T statement)
+ private synchronized <T extends Statement> T trackStatement(final T statement)
{
openStatements.add(statement);
return statement;
}
- private final void closeStatements()
+ @SuppressWarnings("EmptyTryBlock")
+ private synchronized 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)) {
+ try (Statement ignored = openStatements.get(i)) {
// automatic resource cleanup
}
catch (SQLException e) {
- checkException(e);
+ LOGGER.warn("{} - Connection {} marked as broken because of an exception closing open statements during Connection.close()",
+ poolEntry.getPoolName(), delegate);
+ leakTask.cancel();
+ poolEntry.evict("(exception closing Statements during Connection.close())");
+ delegate = ClosedConnection.CLOSED_CONNECTION;
}
}
- synchronized (this) {
- openStatements.clear();
- }
+ openStatements.clear();
}
}
@@ -346,6 +354,14 @@ public abstract class ProxyConnection implements Connection
/** {@inheritDoc} */
@Override
+ public DatabaseMetaData getMetaData() throws SQLException
+ {
+ markCommitStateDirty();
+ return delegate.getMetaData();
+ }
+
+ /** {@inheritDoc} */
+ @Override
public void commit() throws SQLException
{
delegate.commit();
@@ -419,6 +435,15 @@ public abstract class ProxyConnection implements Connection
/** {@inheritDoc} */
@Override
+ public void setSchema(String schema) throws SQLException
+ {
+ delegate.setSchema(schema);
+ dbschema = schema;
+ dirtyBits |= DIRTY_BIT_SCHEMA;
+ }
+
+ /** {@inheritDoc} */
+ @Override
public final boolean isWrapperFor(Class<?> iface) throws SQLException
{
return iface.isInstance(delegate) || (delegate instanceof Wrapper && delegate.isWrapperFor(iface));
@@ -449,24 +474,19 @@ public abstract class ProxyConnection implements Connection
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");
+ InvocationHandler handler = (proxy, method, args) -> {
+ 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
index 026debb..4a074da 100644
--- a/src/main/java/com/zaxxer/hikari/pool/ProxyFactory.java
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyFactory.java
@@ -30,6 +30,7 @@ import com.zaxxer.hikari.util.FastList;
*
* @author Brett Wooldridge
*/
+@SuppressWarnings("unused")
public final class ProxyFactory
{
private ProxyFactory()
@@ -39,13 +40,13 @@ public final class ProxyFactory
/**
* Create a proxy for the specified {@link Connection} instance.
- * @param poolEntry
- * @param connection
- * @param openStatements
- * @param leakTask
- * @param now
- * @param isReadOnly
- * @param isAutoCommit
+ * @param poolEntry the PoolEntry holding pool state
+ * @param connection the raw database Connection
+ * @param openStatements a reusable list to track open Statement instances
+ * @param leakTask the ProxyLeakTask for this connection
+ * @param now the current timestamp
+ * @param isReadOnly the default readOnly state of the connection
+ * @param isAutoCommit the default autoCommit state of the connection
* @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)
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyLeakTask.java b/src/main/java/com/zaxxer/hikari/pool/ProxyLeakTask.java
index 0fdc93e..f72615e 100644
--- a/src/main/java/com/zaxxer/hikari/pool/ProxyLeakTask.java
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyLeakTask.java
@@ -32,10 +32,8 @@ import org.slf4j.LoggerFactory;
class ProxyLeakTask implements Runnable
{
private static final Logger LOGGER = LoggerFactory.getLogger(ProxyLeakTask.class);
- private static final ProxyLeakTask NO_LEAK;
+ static final ProxyLeakTask NO_LEAK;
- private ScheduledExecutorService executorService;
- private long leakDetectionThreshold;
private ScheduledFuture<?> scheduledFuture;
private String connectionName;
private Exception exception;
@@ -45,35 +43,29 @@ class ProxyLeakTask implements Runnable
{
NO_LEAK = new ProxyLeakTask() {
@Override
+ void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold) {}
+
+ @Override
+ public void run() {}
+
+ @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)
+ ProxyLeakTask(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)
+ void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold)
{
- this.leakDetectionThreshold = leakDetectionThreshold;
+ scheduledFuture = executorService.schedule(this, leakDetectionThreshold, TimeUnit.MILLISECONDS);
}
/** {@inheritDoc} */
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyLeakTaskFactory.java b/src/main/java/com/zaxxer/hikari/pool/ProxyLeakTaskFactory.java
new file mode 100644
index 0000000..fde6074
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyLeakTaskFactory.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+/**
+ * A factory for {@link ProxyLeakTask} Runnables that are scheduled in the future to report leaks.
+ *
+ * @author Brett Wooldridge
+ * @author Andreas Brenk
+ */
+class ProxyLeakTaskFactory
+{
+ private ScheduledExecutorService executorService;
+ private long leakDetectionThreshold;
+
+ ProxyLeakTaskFactory(final long leakDetectionThreshold, final ScheduledExecutorService executorService)
+ {
+ this.executorService = executorService;
+ this.leakDetectionThreshold = leakDetectionThreshold;
+ }
+
+ ProxyLeakTask schedule(final PoolEntry poolEntry)
+ {
+ return (leakDetectionThreshold == 0) ? ProxyLeakTask.NO_LEAK : scheduleNewTask(poolEntry);
+ }
+
+ void updateLeakDetectionThreshold(final long leakDetectionThreshold)
+ {
+ this.leakDetectionThreshold = leakDetectionThreshold;
+ }
+
+ private ProxyLeakTask scheduleNewTask(PoolEntry poolEntry) {
+ ProxyLeakTask task = new ProxyLeakTask(poolEntry);
+ task.schedule(executorService, leakDetectionThreshold);
+
+ return task;
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyPreparedStatement.java b/src/main/java/com/zaxxer/hikari/pool/ProxyPreparedStatement.java
index e2d96c9..68e47af 100644
--- a/src/main/java/com/zaxxer/hikari/pool/ProxyPreparedStatement.java
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyPreparedStatement.java
@@ -27,7 +27,7 @@ import java.sql.SQLException;
*/
public abstract class ProxyPreparedStatement extends ProxyStatement implements PreparedStatement
{
- protected ProxyPreparedStatement(ProxyConnection connection, PreparedStatement statement)
+ ProxyPreparedStatement(ProxyConnection connection, PreparedStatement statement)
{
super(connection, statement);
}
@@ -50,7 +50,7 @@ public abstract class ProxyPreparedStatement extends ProxyStatement implements P
{
connection.markCommitStateDirty();
ResultSet resultSet = ((PreparedStatement) delegate).executeQuery();
- return ProxyFactory.getProxyResultSet(connection, this, resultSet);
+ return ProxyFactory.getProxyResultSet(connection, this, resultSet);
}
/** {@inheritDoc} */
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyResultSet.java b/src/main/java/com/zaxxer/hikari/pool/ProxyResultSet.java
index 1933979..e2c4950 100644
--- a/src/main/java/com/zaxxer/hikari/pool/ProxyResultSet.java
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyResultSet.java
@@ -19,7 +19,6 @@ 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.
@@ -30,7 +29,7 @@ public abstract class ProxyResultSet implements ResultSet
{
protected final ProxyConnection connection;
protected final ProxyStatement statement;
- protected final ResultSet delegate;
+ final ResultSet delegate;
protected ProxyResultSet(ProxyConnection connection, ProxyStatement statement, ResultSet resultSet)
{
@@ -39,6 +38,7 @@ public abstract class ProxyResultSet implements ResultSet
this.delegate = resultSet;
}
+ @SuppressWarnings("unused")
final SQLException checkException(SQLException e)
{
return connection.checkException(e);
@@ -48,10 +48,7 @@ public abstract class ProxyResultSet implements ResultSet
@Override
public String toString()
{
- return new StringBuilder(64)
- .append(this.getClass().getSimpleName()).append('@').append(System.identityHashCode(this))
- .append(" wrapping ")
- .append(delegate).toString();
+ return this.getClass().getSimpleName() + '@' + System.identityHashCode(this) + " wrapping " + delegate;
}
// **********************************************************************
@@ -97,7 +94,7 @@ public abstract class ProxyResultSet implements ResultSet
if (iface.isInstance(delegate)) {
return (T) delegate;
}
- else if (delegate instanceof Wrapper) {
+ else if (delegate != null) {
return delegate.unwrap(iface);
}
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyStatement.java b/src/main/java/com/zaxxer/hikari/pool/ProxyStatement.java
index 1d92cd8..bb5ac69 100644
--- a/src/main/java/com/zaxxer/hikari/pool/ProxyStatement.java
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyStatement.java
@@ -20,7 +20,6 @@ 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.
@@ -30,17 +29,18 @@ import java.sql.Wrapper;
public abstract class ProxyStatement implements Statement
{
protected final ProxyConnection connection;
- protected final Statement delegate;
+ final Statement delegate;
private boolean isClosed;
private ResultSet proxyResultSet;
- protected ProxyStatement(ProxyConnection connection, Statement statement)
+ ProxyStatement(ProxyConnection connection, Statement statement)
{
this.connection = connection;
this.delegate = statement;
}
+ @SuppressWarnings("unused")
final SQLException checkException(SQLException e)
{
return connection.checkException(e);
@@ -51,10 +51,7 @@ public abstract class ProxyStatement implements Statement
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();
+ return this.getClass().getSimpleName() + '@' + System.identityHashCode(this) + " wrapping " + delegateToString;
}
// **********************************************************************
@@ -65,11 +62,14 @@ public abstract class ProxyStatement implements Statement
@Override
public final void close() throws SQLException
{
- if (isClosed) {
- return;
+ synchronized (this) {
+ if (isClosed) {
+ return;
+ }
+
+ isClosed = true;
}
- isClosed = true;
connection.untrackStatement(delegate);
try {
@@ -231,7 +231,7 @@ public abstract class ProxyStatement implements Statement
if (iface.isInstance(delegate)) {
return (T) delegate;
}
- else if (delegate instanceof Wrapper) {
+ else if (delegate != null) {
return delegate.unwrap(iface);
}
diff --git a/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java b/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java
index ac0ccb1..9822563 100755
--- a/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java
+++ b/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java
@@ -15,20 +15,23 @@
*/
package com.zaxxer.hikari.util;
+import static java.lang.Thread.yield;
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.concurrent.locks.LockSupport.parkNanos;
+
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.Collections;
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;
@@ -73,7 +76,7 @@ public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseab
private final SynchronousQueue<T> handoffQueue;
- public static interface IConcurrentBagEntry
+ public interface IConcurrentBagEntry
{
int STATE_NOT_IN_USE = 0;
int STATE_IN_USE = 1;
@@ -85,9 +88,9 @@ public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseab
int getState();
}
- public static interface IBagStateListener
+ public interface IBagStateListener
{
- Future<Boolean> addBagItem(int waiting);
+ void addBagItem(int waiting);
}
/**
@@ -147,7 +150,7 @@ public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseab
}
listener.addBagItem(waiting);
-
+
timeout = timeUnit.toNanos(timeout);
do {
final long start = currentTime();
@@ -179,11 +182,16 @@ public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseab
{
bagEntry.setState(STATE_NOT_IN_USE);
- while (waiters.get() > 0) {
- if (handoffQueue.offer(bagEntry)) {
+ for (int i = 0; waiters.get() > 0; i++) {
+ if (bagEntry.getState() != STATE_NOT_IN_USE || handoffQueue.offer(bagEntry)) {
return;
}
- yield();
+ else if ((i & 0xff) == 0xff) {
+ parkNanos(MICROSECONDS.toNanos(10));
+ }
+ else {
+ yield();
+ }
}
final List<Object> threadLocalList = threadList.get();
@@ -254,7 +262,9 @@ public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseab
*/
public List<T> values(final int state)
{
- return sharedList.stream().filter(e -> e.getState() == state).collect(Collectors.toList());
+ final List<T> list = sharedList.stream().filter(e -> e.getState() == state).collect(Collectors.toList());
+ Collections.reverse(list);
+ return list;
}
/**
diff --git a/src/main/java/com/zaxxer/hikari/util/DriverDataSource.java b/src/main/java/com/zaxxer/hikari/util/DriverDataSource.java
index 9aa9b6b..b40decf 100644
--- a/src/main/java/com/zaxxer/hikari/util/DriverDataSource.java
+++ b/src/main/java/com/zaxxer/hikari/util/DriverDataSource.java
@@ -66,12 +66,29 @@ public final class DriverDataSource implements DataSource
if (driver == null) {
LOGGER.warn("Registered driver with driverClassName={} was not found, trying direct instantiation.", driverClassName);
+ Class<?> driverClass = null;
try {
- Class<?> driverClass = this.getClass().getClassLoader().loadClass(driverClassName);
- driver = (Driver) driverClass.newInstance();
+ driverClass = this.getClass().getClassLoader().loadClass(driverClassName);
+ LOGGER.debug("Driver class found in the HikariConfig class classloader {}", this.getClass().getClassLoader());
+ } catch (ClassNotFoundException e) {
+ ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader();
+ if (threadContextClassLoader != null && threadContextClassLoader != this.getClass().getClassLoader()) {
+ try {
+ driverClass = threadContextClassLoader.loadClass(driverClassName);
+ LOGGER.debug("Driver class found in Thread context class loader {}", threadContextClassLoader);
+ } catch (ClassNotFoundException e1) {
+ LOGGER.warn("Failed to load class of driverClassName {} in either of HikariConfig class classloader {} or Thread context classloader {}", driverClassName, this.getClass().getClassLoader(), threadContextClassLoader);
+ }
+ } else {
+ LOGGER.warn("Failed to load class of driverClassName {} in HikariConfig class classloader {}", driverClassName, this.getClass().getClassLoader());
+ }
}
- catch (Exception e) {
- LOGGER.warn("Failed to create instance of driver class {}, trying jdbcUrl resolution", driverClassName, e);
+ if (driverClass != null) {
+ try {
+ driver = (Driver) driverClass.newInstance();
+ } catch (Exception e) {
+ LOGGER.warn("Failed to create instance of driver class {}, trying jdbcUrl resolution", driverClassName, e);
+ }
}
}
}
diff --git a/src/main/java/com/zaxxer/hikari/util/QueuedSequenceSynchronizer.java b/src/main/java/com/zaxxer/hikari/util/QueuedSequenceSynchronizer.java
deleted file mode 100644
index eac7ec7..0000000
--- a/src/main/java/com/zaxxer/hikari/util/QueuedSequenceSynchronizer.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * 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
deleted file mode 100644
index b7abd3c..0000000
--- a/src/main/java/com/zaxxer/hikari/util/Sequence.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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/test/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerTest.java b/src/test/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerTest.java
new file mode 100755
index 0000000..761ef12
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerTest.java
@@ -0,0 +1,39 @@
+package com.zaxxer.hikari.metrics.micrometer;
+
+import com.zaxxer.hikari.metrics.PoolStats;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MicrometerMetricsTrackerTest {
+
+ private MeterRegistry mockMeterRegistry = new SimpleMeterRegistry();
+
+ private MicrometerMetricsTracker testee;
+
+ @Before
+ public void setup(){
+ testee = new MicrometerMetricsTracker("mypool", new PoolStats(1000L) {
+ @Override
+ protected void update() {
+ // nothing
+ }
+ }, mockMeterRegistry);
+ }
+
+ @Test
+ public void close() throws Exception {
+ Assert.assertNotNull(mockMeterRegistry.find("Wait"));
+ Assert.assertNotNull(mockMeterRegistry.find("Usage"));
+ Assert.assertNotNull(mockMeterRegistry.find("ConnectionCreation"));
+ Assert.assertNotNull(mockMeterRegistry.find("ConnectionTimeoutRate"));
+ Assert.assertNotNull(mockMeterRegistry.find("TotalConnections"));
+ Assert.assertNotNull(mockMeterRegistry.find("IdleConnections"));
+ Assert.assertNotNull(mockMeterRegistry.find("ActiveConnections"));
+ Assert.assertNotNull(mockMeterRegistry.find("PendingConnections"));
+
+ testee.close();
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java b/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java
index 46b2e74..63746a3 100644
--- a/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java
+++ b/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java
@@ -60,10 +60,11 @@ public class HikariCPCollectorTest {
StubConnection.slowCreate = true;
try (HikariDataSource ds = new HikariDataSource(config)) {
- assertThat(getValue("hikaricp_active_connections", "HikariPool-1"), is(0.0));
- assertThat(getValue("hikaricp_idle_connections", "HikariPool-1"), is(0.0));
- assertThat(getValue("hikaricp_pending_threads", "HikariPool-1"), is(0.0));
- assertThat(getValue("hikaricp_connections", "HikariPool-1"), is(0.0));
+ String poolName = ds.getHikariConfigMXBean().getPoolName();
+ assertThat(getValue("hikaricp_active_connections", poolName), is(0.0));
+ assertThat(getValue("hikaricp_idle_connections", poolName), is(0.0));
+ assertThat(getValue("hikaricp_pending_threads", poolName), is(0.0));
+ assertThat(getValue("hikaricp_connections", poolName), is(0.0));
}
finally {
StubConnection.slowCreate = false;
@@ -105,7 +106,7 @@ public class HikariCPCollectorTest {
try (Connection connection1 = ds.getConnection()) {
// close immediately
}
-
+
assertThat(getValue("hikaricp_active_connections", "connectionClosed"), is(0.0));
assertThat(getValue("hikaricp_idle_connections", "connectionClosed"), is(1.0));
assertThat(getValue("hikaricp_pending_threads", "connectionClosed"), is(0.0));
diff --git a/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java b/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java
index a7b0b03..079cfb5 100644
--- a/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java
+++ b/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java
@@ -33,6 +33,7 @@ import com.zaxxer.hikari.HikariDataSource;
import io.prometheus.client.CollectorRegistry;
public class PrometheusMetricsTrackerTest {
+
@Test
public void recordConnectionTimeout() throws Exception {
HikariConfig config = newHikariConfig();
@@ -40,13 +41,13 @@ public class PrometheusMetricsTrackerTest {
config.setJdbcUrl("jdbc:h2:mem:");
config.setMaximumPoolSize(2);
config.setConnectionTimeout(250);
-
+
String[] labelNames = {"pool"};
String[] labelValues = {config.getPoolName()};
try (HikariDataSource hikariDataSource = new HikariDataSource(config)) {
try (Connection connection1 = hikariDataSource.getConnection();
- Connection connection2 = hikariDataSource.getConnection()) {
+ Connection connection2 = hikariDataSource.getConnection()) {
try (Connection connection3 = hikariDataSource.getConnection()) {
}
catch (SQLTransientConnectionException ignored) {
@@ -54,13 +55,13 @@ public class PrometheusMetricsTrackerTest {
}
assertThat(CollectorRegistry.defaultRegistry.getSampleValue(
- "hikaricp_connection_timeout_count",
+ "hikaricp_connection_timeout_total",
labelNames,
labelValues), is(1.0));
assertThat(CollectorRegistry.defaultRegistry.getSampleValue(
"hikaricp_connection_acquired_nanos_count",
labelNames,
- labelValues), is(equalTo(2.0)));
+ labelValues), is(equalTo(3.0)));
assertTrue(CollectorRegistry.defaultRegistry.getSampleValue(
"hikaricp_connection_acquired_nanos_sum",
labelNames,
@@ -75,4 +76,39 @@ public class PrometheusMetricsTrackerTest {
labelValues) > 0.0);
}
}
+
+ @Test
+ public void testMultiplePoolName() throws Exception {
+ String[] labelNames = {"pool"};
+
+ HikariConfig config = newHikariConfig();
+ config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
+ config.setPoolName("first");
+ config.setJdbcUrl("jdbc:h2:mem:");
+ config.setMaximumPoolSize(2);
+ config.setConnectionTimeout(250);
+ String[] labelValues1 = {config.getPoolName()};
+
+ try (HikariDataSource ignored = new HikariDataSource(config)) {
+ assertThat(CollectorRegistry.defaultRegistry.getSampleValue(
+ "hikaricp_connection_timeout_total",
+ labelNames,
+ labelValues1), is(0.0));
+
+ HikariConfig config2 = newHikariConfig();
+ config2.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
+ config2.setPoolName("second");
+ config2.setJdbcUrl("jdbc:h2:mem:");
+ config2.setMaximumPoolSize(4);
+ config2.setConnectionTimeout(250);
+ String[] labelValues2 = {config2.getPoolName()};
+
+ try (HikariDataSource ignored2 = new HikariDataSource(config2)) {
+ assertThat(CollectorRegistry.defaultRegistry.getSampleValue(
+ "hikaricp_connection_timeout_total",
+ labelNames,
+ labelValues2), is(0.0));
+ }
+ }
+ }
}
diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java b/src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java
index 5c48f85..2092611 100644
--- a/src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java
+++ b/src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java
@@ -45,7 +45,7 @@ import java.util.Calendar;
*/
public class StubPreparedStatement extends StubStatement implements PreparedStatement
{
- public StubPreparedStatement(Connection connection)
+ StubPreparedStatement(Connection connection)
{
super(connection);
}
diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubStatement.java b/src/test/java/com/zaxxer/hikari/mocks/StubStatement.java
index 73fec69..99e7b13 100644
--- a/src/test/java/com/zaxxer/hikari/mocks/StubStatement.java
+++ b/src/test/java/com/zaxxer/hikari/mocks/StubStatement.java
@@ -16,6 +16,8 @@
package com.zaxxer.hikari.mocks;
+import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
+
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -30,6 +32,8 @@ import java.sql.Statement;
public class StubStatement implements Statement
{
public static volatile boolean oldDriver;
+
+ private static volatile long simulatedQueryTime;
private boolean closed;
private Connection connection;
@@ -37,6 +41,10 @@ public class StubStatement implements Statement
this.connection = connection;
}
+ public static void setSimulatedQueryTime(long time) {
+ simulatedQueryTime = time;
+ }
+
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
@Override
@@ -168,6 +176,9 @@ public class StubStatement implements Statement
public boolean execute(String sql) throws SQLException
{
checkClosed();
+ if (simulatedQueryTime > 0) {
+ quietlySleep(simulatedQueryTime);
+ }
return false;
}
diff --git a/src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java b/src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java
index 761a802..a41b916 100644
--- a/src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java
+++ b/src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java
@@ -17,6 +17,10 @@ package com.zaxxer.hikari.osgi;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.model.InitializationError;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.PaxExam;
@@ -24,9 +28,9 @@ import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import javax.inject.Inject;
-
import java.io.File;
+import static com.zaxxer.hikari.pool.TestElf.isJava9;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.CoreOptions.*;
@@ -34,58 +38,76 @@ import static org.ops4j.pax.exam.CoreOptions.*;
/**
* @author lburgazzoli
*/
-@RunWith(PaxExam.class)
+@RunWith(OSGiBundleTest.ConditionalPaxExam.class)
public class OSGiBundleTest
{
- @Inject
- BundleContext context;
+ @Test
+ public void checkInject()
+ {
+ assertNotNull(context);
+ }
+
+ @Test
+ public void checkBundle()
+ {
+ Boolean bundleFound = false;
+ Boolean bundleActive = false;
- @Configuration
- public Option[] config()
- {
- return options(
- systemProperty("org.osgi.framework.storage.clean").value("true"),
- systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("WARN"),
- mavenBundle("org.slf4j","slf4j-api","1.7.5"),
- mavenBundle("org.slf4j","slf4j-simple","1.7.5").noStart(),
- mavenBundle("org.javassist", "javassist", "3.19.0-GA"),
- new File("target/classes").exists()
- ? bundle("reference:file:target/classes")
- : bundle("reference:file:../target/classes"),
- junitBundles(),
- cleanCaches()
- );
- }
+ Bundle[] bundles = context.getBundles();
+ for (Bundle bundle : bundles) {
+ if (bundle != null) {
+ if (bundle.getSymbolicName().equals("com.zaxxer.HikariCP")) {
+ bundleFound = true;
+ if (bundle.getState() == Bundle.ACTIVE) {
+ bundleActive = true;
+ }
+ }
+ }
+ }
- @Test
- public void checkInject()
- {
- assertNotNull(context);
- }
+ assertTrue(bundleFound);
+ assertTrue(bundleActive);
+ }
- @Test
- public void checkBundle()
- {
- Boolean bundleFound = false;
- Boolean bundleActive = false;
+ @Inject
+ BundleContext context;
- Bundle[] bundles = context.getBundles();
- for(Bundle bundle : bundles)
- {
- if(bundle != null)
- {
- if(bundle.getSymbolicName().equals("com.zaxxer.HikariCP"))
- {
- bundleFound = true;
- if(bundle.getState() == Bundle.ACTIVE)
- {
- bundleActive = true;
- }
- }
- }
- }
+ @Configuration
+ public Option[] config()
+ {
+ return options(
+ systemProperty("org.osgi.framework.storage.clean").value("true"),
+ systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("WARN"),
+ mavenBundle("org.slf4j", "slf4j-api", "1.7.5"),
+ mavenBundle("org.slf4j", "slf4j-simple", "1.7.5").noStart(),
+ new File("target/classes").exists()
+ ? bundle("reference:file:target/classes")
+ : bundle("reference:file:../target/classes"),
+ junitBundles(),
+ cleanCaches()
+ );
+ }
+
+ public static class ConditionalPaxExam extends PaxExam
+ {
+ public ConditionalPaxExam(Class<?> klass) throws InitializationError {
+ super(klass);
+ }
+
+ @Override
+ public void run(RunNotifier notifier) {
+ if (!isJava9()) {
+ super.run(notifier);
+ }
+ }
+
+ @Override
+ public void filter(Filter filter) throws NoTestsRemainException {
+ if (isJava9()) {
+ throw new NoTestsRemainException();
+ }
- assertTrue(bundleFound);
- assertTrue(bundleActive);
- }
+ super.filter(filter);
+ }
+ }
}
diff --git a/src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java b/src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java
index e66ba58..8585485 100755
--- a/src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java
+++ b/src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java
@@ -45,9 +45,9 @@ import com.zaxxer.hikari.mocks.StubDataSource;
*/
public class ConnectionPoolSizeVsThreadsTest {
- public static final Logger LOGGER = LoggerFactory.getLogger(ConnectionPoolSizeVsThreadsTest.class);
+ private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionPoolSizeVsThreadsTest.class);
- public static final int ITERATIONS = 50_000;
+ private static final int ITERATIONS = 50_000;
@Test
public void testPoolSizeAboutSameSizeAsThreadCount() throws Exception {
diff --git a/src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java b/src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java
index 31f8363..c2bd000 100755
--- a/src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java
+++ b/src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java
@@ -51,7 +51,7 @@ public class ConnectionRaceConditionTest
config.setMinimumIdle(0);
config.setMaximumPoolSize(10);
config.setInitializationFailTimeout(Long.MAX_VALUE);
- config.setConnectionTimeout(2500);
+ config.setConnectionTimeout(5000);
config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
setSlf4jLogLevel(ConcurrentBag.class, Level.INFO);
diff --git a/src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java b/src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java
index 1a7caa7..f62de43 100644
--- a/src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java
+++ b/src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java
@@ -59,7 +59,7 @@ public class HouseKeeperCleanupTest
config.setInitializationFailTimeout(Long.MAX_VALUE);
config.setConnectionTimeout(2500);
config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
- config.setScheduledExecutorService(executor);
+ config.setScheduledExecutor(executor);
HikariConfig config2 = newHikariConfig();
config.copyState(config2);
diff --git a/src/test/java/com/zaxxer/hikari/pool/MetricsTrackerTest.java b/src/test/java/com/zaxxer/hikari/pool/MetricsTrackerTest.java
new file mode 100644
index 0000000..494fd4d
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/MetricsTrackerTest.java
@@ -0,0 +1,86 @@
+package com.zaxxer.hikari.pool;
+
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.metrics.IMetricsTracker;
+import com.zaxxer.hikari.mocks.StubDataSource;
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.SQLTransientConnectionException;
+import java.util.concurrent.TimeUnit;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource;
+import static junit.framework.TestCase.fail;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author wvuong@chariotsolutions.com on 2/16/17.
+ */
+public class MetricsTrackerTest
+{
+
+ @Test(expected = SQLTransientConnectionException.class)
+ public void connectionTimeoutIsRecorded() throws Exception
+ {
+ int timeoutMillis = 1000;
+ int timeToCreateNewConnectionMillis = timeoutMillis * 2;
+
+ StubDataSource stubDataSource = new StubDataSource();
+ stubDataSource.setConnectionAcquistionTime(timeToCreateNewConnectionMillis);
+
+ StubMetricsTracker metricsTracker = new StubMetricsTracker();
+
+ try (HikariDataSource ds = newHikariDataSource()) {
+ ds.setMinimumIdle(0);
+ ds.setMaximumPoolSize(1);
+ ds.setConnectionTimeout(timeoutMillis);
+ ds.setDataSource(stubDataSource);
+ ds.setMetricsTrackerFactory((poolName, poolStats) -> metricsTracker);
+
+ try (Connection c = ds.getConnection()) {
+ fail("Connection shouldn't have been successfully created due to configured connection timeout");
+
+ } finally {
+ // assert that connection timeout was measured
+ assertThat(metricsTracker.connectionTimeoutRecorded, is(true));
+ // assert that measured time to acquire connection should be roughly equal or greater than the configured connection timeout time
+ assertTrue(metricsTracker.connectionAcquiredNanos >= TimeUnit.NANOSECONDS.convert(timeoutMillis, TimeUnit.MILLISECONDS));
+ }
+ }
+ }
+
+ private static class StubMetricsTracker implements IMetricsTracker
+ {
+
+ private Long connectionCreatedMillis;
+ private Long connectionAcquiredNanos;
+ private Long connectionBorrowedMillis;
+ private boolean connectionTimeoutRecorded;
+
+ @Override
+ public void recordConnectionCreatedMillis(long connectionCreatedMillis)
+ {
+ this.connectionCreatedMillis = connectionCreatedMillis;
+ }
+
+ @Override
+ public void recordConnectionAcquiredNanos(long elapsedAcquiredNanos)
+ {
+ this.connectionAcquiredNanos = elapsedAcquiredNanos;
+ }
+
+ @Override
+ public void recordConnectionUsageMillis(long elapsedBorrowedMillis)
+ {
+ this.connectionBorrowedMillis = elapsedBorrowedMillis;
+ }
+
+ @Override
+ public void recordConnectionTimeout()
+ {
+ this.connectionTimeoutRecorded = true;
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/MiscTest.java b/src/test/java/com/zaxxer/hikari/pool/MiscTest.java
index f9698ef..2791f3c 100644
--- a/src/test/java/com/zaxxer/hikari/pool/MiscTest.java
+++ b/src/test/java/com/zaxxer/hikari/pool/MiscTest.java
@@ -102,7 +102,7 @@ public class MiscTest
try (PrintStream ps = new PrintStream(baos, true)) {
setSlf4jTargetStream(Class.forName("com.zaxxer.hikari.pool.ProxyLeakTask"), ps);
setConfigUnitTest(true);
-
+
HikariConfig config = newHikariConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(4);
@@ -110,11 +110,11 @@ public class MiscTest
config.setMetricRegistry(null);
config.setLeakDetectionThreshold(TimeUnit.SECONDS.toMillis(1));
config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
+
try (HikariDataSource ds = new HikariDataSource(config)) {
setSlf4jLogLevel(HikariPool.class, Level.DEBUG);
getPool(ds).logPoolState();
-
+
try (Connection connection = ds.getConnection()) {
quietlySleep(SECONDS.toMillis(4));
connection.close();
@@ -128,6 +128,7 @@ public class MiscTest
finally
{
setConfigUnitTest(false);
+ setSlf4jLogLevel(HikariPool.class, Level.INFO);
}
}
}
diff --git a/src/test/java/com/zaxxer/hikari/pool/SaturatedPoolTest830.java b/src/test/java/com/zaxxer/hikari/pool/SaturatedPoolTest830.java
new file mode 100644
index 0000000..db8f970
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/SaturatedPoolTest830.java
@@ -0,0 +1,156 @@
+/*
+ * 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.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.getConcurrentBag;
+import static com.zaxxer.hikari.pool.TestElf.getPool;
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.setSlf4jLogLevel;
+import static com.zaxxer.hikari.util.ClockSource.currentTime;
+import static com.zaxxer.hikari.util.ClockSource.elapsedMillis;
+import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
+import static java.lang.Math.round;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Arrays;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.logging.log4j.Level;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.mocks.StubConnection;
+import com.zaxxer.hikari.mocks.StubStatement;
+
+/**
+ * @author Brett Wooldridge
+ */
+public class SaturatedPoolTest830
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(SaturatedPoolTest830.class);
+ private static final int MAX_POOL_SIZE = 10;
+
+ @Test
+ public void saturatedPoolTest() throws Exception {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(5);
+ config.setMaximumPoolSize(MAX_POOL_SIZE);
+ config.setInitializationFailTimeout(Long.MAX_VALUE);
+ config.setConnectionTimeout(1000);
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ StubConnection.slowCreate = true;
+ StubStatement.setSimulatedQueryTime(1000);
+ setSlf4jLogLevel(HikariPool.class, Level.DEBUG);
+ System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "5000");
+
+ final long start = currentTime();
+
+ try (final HikariDataSource ds = new HikariDataSource(config)) {
+ LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
+ ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 50 /*core*/, 50 /*max*/, 2 /*keepalive*/, SECONDS, queue, new ThreadPoolExecutor.CallerRunsPolicy());
+ threadPool.allowCoreThreadTimeOut(true);
+
+ AtomicInteger windowIndex = new AtomicInteger();
+ boolean[] failureWindow = new boolean[100];
+ Arrays.fill(failureWindow, true);
+
+ // Initial saturation
+ for (int i = 0; i < 50; i++) {
+ threadPool.execute(() -> {
+ try (Connection conn = ds.getConnection();
+ Statement stmt = conn.createStatement()) {
+ stmt.execute("SELECT bogus FROM imaginary");
+ }
+ catch (SQLException e) {
+ LOGGER.info(e.getMessage());
+ }
+ });
+ }
+
+ long sleep = 80;
+outer: while (true) {
+ quietlySleep(sleep);
+
+ if (elapsedMillis(start) > SECONDS.toMillis(12) && sleep < 100) {
+ sleep = 100;
+ LOGGER.warn("Switching to 100ms sleep");
+ }
+ else if (elapsedMillis(start) > SECONDS.toMillis(6) && sleep < 90) {
+ sleep = 90;
+ LOGGER.warn("Switching to 90ms sleep");
+ }
+
+ threadPool.execute(() -> {
+ int ndx = windowIndex.incrementAndGet() % failureWindow.length;
+
+ try (Connection conn = ds.getConnection();
+ Statement stmt = conn.createStatement()) {
+ stmt.execute("SELECT bogus FROM imaginary");
+ failureWindow[ndx] = false;
+ }
+ catch (SQLException e) {
+ LOGGER.info(e.getMessage());
+ failureWindow[ndx] = true;
+ }
+ });
+
+ for (int i = 0; i < failureWindow.length; i++) {
+ if (failureWindow[i]) {
+ if (elapsedMillis(start) % (SECONDS.toMillis(1) - sleep) < sleep) {
+ LOGGER.info("Active threads {}, submissions per second {}, waiting threads {}",
+ threadPool.getActiveCount(),
+ SECONDS.toMillis(1) / sleep,
+ getPool(ds).getThreadsAwaitingConnection());
+ }
+ continue outer;
+ }
+ }
+
+ LOGGER.info("Timeouts have subsided.");
+ LOGGER.info("Active threads {}, submissions per second {}, waiting threads {}",
+ threadPool.getActiveCount(),
+ SECONDS.toMillis(1) / sleep,
+ getPool(ds).getThreadsAwaitingConnection());
+ break;
+ }
+
+ LOGGER.info("Waiting for completion of {} active tasks.", threadPool.getActiveCount());
+ while (getPool(ds).getActiveConnections() > 0) {
+ quietlySleep(50);
+ }
+
+ assertEquals("Rate not in balance at 10req/s", SECONDS.toMillis(1) / sleep, 10L);
+ }
+ finally {
+ StubStatement.setSimulatedQueryTime(0);
+ StubConnection.slowCreate = false;
+ System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs");
+ setSlf4jLogLevel(HikariPool.class, Level.INFO);
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java b/src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java
index 15f8974..2760543 100644
--- a/src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java
+++ b/src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java
@@ -251,9 +251,9 @@ public class TestConnectionTimeoutRetry
Connection connection5 = ds.getConnection();
Connection connection6 = ds.getConnection();
Connection connection7 = ds.getConnection()) {
-
+
sleep(1300);
-
+
assertSame("Total connections not as expected", 10, pool.getTotalConnections());
assertSame("Idle connections not as expected", 3, pool.getIdleConnections());
}
@@ -273,5 +273,6 @@ public class TestConnectionTimeoutRetry
public void after()
{
System.getProperties().remove("com.zaxxer.hikari.housekeeping.periodMs");
+ setSlf4jLogLevel(HikariPool.class, Level.INFO);
}
}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestElf.java b/src/test/java/com/zaxxer/hikari/pool/TestElf.java
index 6438b11..7ec2925 100644
--- a/src/test/java/com/zaxxer/hikari/pool/TestElf.java
+++ b/src/test/java/com/zaxxer/hikari/pool/TestElf.java
@@ -19,7 +19,6 @@ package com.zaxxer.hikari.pool;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.sql.Connection;
-import java.util.HashMap;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Appender;
@@ -32,6 +31,7 @@ import org.slf4j.LoggerFactory;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.util.ConcurrentBag;
/**
* Utility methods for testing.
@@ -44,6 +44,10 @@ public final class TestElf
// default constructor
}
+ public static boolean isJava9() {
+ return System.getProperty("java.version").startsWith("9");
+ }
+
public static HikariPool getPool(HikariDataSource ds)
{
try {
@@ -56,20 +60,19 @@ public final class TestElf
}
}
- @SuppressWarnings("unchecked")
- public static HashMap<Object, HikariPool> getMultiPool(HikariDataSource ds)
+ static ConcurrentBag<?> getConcurrentBag(HikariDataSource ds)
{
try {
- Field field = ds.getClass().getDeclaredField("multiPool");
+ Field field = HikariPool.class.getDeclaredField("connectionBag");
field.setAccessible(true);
- return (HashMap<Object, HikariPool>) field.get(ds);
+ return (ConcurrentBag<?>) field.get(getPool(ds));
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
- public static boolean getConnectionCommitDirtyState(Connection connection)
+ static boolean getConnectionCommitDirtyState(Connection connection)
{
try {
Field field = ProxyConnection.class.getDeclaredField("isCommitStateDirty");
@@ -81,7 +84,7 @@ public final class TestElf
}
}
- public static void setConfigUnitTest(boolean unitTest)
+ static void setConfigUnitTest(boolean unitTest)
{
try {
Field field = HikariConfig.class.getDeclaredField("unitTest");
@@ -93,7 +96,7 @@ public final class TestElf
}
}
- public static void setSlf4jTargetStream(Class<?> clazz, PrintStream stream)
+ static void setSlf4jTargetStream(Class<?> clazz, PrintStream stream)
{
try {
Log4jLogger log4Jlogger = (Log4jLogger) LoggerFactory.getLogger(clazz);
@@ -114,7 +117,7 @@ public final class TestElf
}
}
- public static void setSlf4jLogLevel(Class<?> clazz, Level logLevel)
+ static void setSlf4jLogLevel(Class<?> clazz, Level logLevel)
{
try {
Log4jLogger log4Jlogger = (Log4jLogger) LoggerFactory.getLogger(clazz);
@@ -144,7 +147,7 @@ public final class TestElf
return config;
}
- public static HikariDataSource newHikariDataSource()
+ static HikariDataSource newHikariDataSource()
{
final StackTraceElement callerStackTrace = Thread.currentThread().getStackTrace()[2];
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestJNDI.java b/src/test/java/com/zaxxer/hikari/pool/TestJNDI.java
index b5cd40c..6e041cb 100644
--- a/src/test/java/com/zaxxer/hikari/pool/TestJNDI.java
+++ b/src/test/java/com/zaxxer/hikari/pool/TestJNDI.java
@@ -15,17 +15,21 @@
*/
package com.zaxxer.hikari.pool;
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
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 com.zaxxer.hikari.HikariConfig;
import org.junit.Test;
import org.osjava.sj.jndi.AbstractContext;
@@ -33,6 +37,8 @@ import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariJNDIFactory;
import com.zaxxer.hikari.mocks.StubDataSource;
+import java.sql.Connection;
+
public class TestJNDI
{
@Test
@@ -94,6 +100,27 @@ public class TestJNDI
}
}
+ @Test
+ public void testJndiLookup4() throws Exception
+ {
+ System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.osjava.sj.memory.MemoryContextFactory");
+ System.setProperty("org.osjava.sj.jndi.shared", "true");
+ InitialContext ic = new InitialContext();
+
+ StubDataSource ds = new StubDataSource();
+
+ Context subcontext = ic.createSubcontext("java:/comp/env/jdbc");
+ subcontext.bind("java:/comp/env/jdbc/myDS", ds);
+
+ HikariConfig config = newHikariConfig();
+ config.setDataSourceJNDI("java:/comp/env/jdbc/myDS");
+
+ try (HikariDataSource hds = new HikariDataSource(config);
+ Connection conn = hds.getConnection()) {
+ assertNotNull(conn);
+ }
+ }
+
private class BogusContext extends AbstractContext
{
@Override
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestMBean.java b/src/test/java/com/zaxxer/hikari/pool/TestMBean.java
index 55ba733..a77c960 100644
--- a/src/test/java/com/zaxxer/hikari/pool/TestMBean.java
+++ b/src/test/java/com/zaxxer/hikari/pool/TestMBean.java
@@ -15,30 +15,83 @@
*/
package com.zaxxer.hikari.pool;
-import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.HikariPoolMXBean;
+import org.junit.Test;
+import javax.management.JMX;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import java.lang.management.ManagementFactory;
+import java.sql.Connection;
import java.sql.SQLException;
+import java.util.concurrent.TimeUnit;
-import org.junit.Test;
-
-import com.zaxxer.hikari.HikariConfig;
-import com.zaxxer.hikari.HikariDataSource;
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static org.junit.Assert.assertEquals;
public class TestMBean
{
- @Test
- public void testMBeanRegistration() throws SQLException
- {
- HikariConfig config = newHikariConfig();
- config.setMinimumIdle(0);
- config.setMaximumPoolSize(1);
- config.setRegisterMbeans(true);
- config.setConnectionTimeout(2800);
- config.setConnectionTestQuery("VALUES 1");
- config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
- try (HikariDataSource ds = new HikariDataSource(config)) {
- // Close immediately
- }
- }
+ @Test
+ public void testMBeanRegistration() throws SQLException {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(1);
+ config.setRegisterMbeans(true);
+ config.setConnectionTimeout(2800);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ // Close immediately
+ }
+ }
+
+ @Test
+ public void testMBeanReporting() throws SQLException, InterruptedException, MalformedObjectNameException {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(3);
+ config.setMaximumPoolSize(5);
+ config.setRegisterMbeans(true);
+ config.setConnectionTimeout(2800);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "100");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+
+ ds.setIdleTimeout(3000);
+
+ TimeUnit.SECONDS.sleep(1);
+
+ MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
+ ObjectName poolName = new ObjectName("com.zaxxer.hikari:type=Pool (testMBeanReporting)");
+ HikariPoolMXBean hikariPoolMXBean = JMX.newMXBeanProxy(mBeanServer, poolName, HikariPoolMXBean.class);
+
+ assertEquals(0, hikariPoolMXBean.getActiveConnections());
+ assertEquals(3, hikariPoolMXBean.getIdleConnections());
+
+ try (Connection connection = ds.getConnection()) {
+ assertEquals(1, hikariPoolMXBean.getActiveConnections());
+
+ TimeUnit.SECONDS.sleep(1);
+
+ assertEquals(3, hikariPoolMXBean.getIdleConnections());
+ assertEquals(4, hikariPoolMXBean.getTotalConnections());
+ }
+
+ TimeUnit.SECONDS.sleep(2);
+
+ assertEquals(0, hikariPoolMXBean.getActiveConnections());
+ assertEquals(3, hikariPoolMXBean.getIdleConnections());
+ assertEquals(3, hikariPoolMXBean.getTotalConnections());
+
+ }
+ finally {
+ System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs");
+ }
+ }
}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestMetrics.java b/src/test/java/com/zaxxer/hikari/pool/TestMetrics.java
index fcba58e..775bb49 100644
--- a/src/test/java/com/zaxxer/hikari/pool/TestMetrics.java
+++ b/src/test/java/com/zaxxer/hikari/pool/TestMetrics.java
@@ -44,8 +44,6 @@ import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
import com.zaxxer.hikari.metrics.dropwizard.CodahaleMetricsTrackerFactory;
import com.zaxxer.hikari.util.UtilityElf;
-import shaded.org.codehaus.plexus.interpolation.os.Os;
-
/**
* Test HikariCP/CodaHale metrics integration.
*
@@ -85,7 +83,7 @@ public class TestMetrics
@Test
public void testMetricUsage() throws SQLException
{
- assumeFalse(Os.isFamily(Os.FAMILY_WINDOWS));
+ assumeFalse(System.getProperty("os.name").contains("Windows"));
MetricRegistry metricRegistry = new MetricRegistry();
HikariConfig config = newHikariConfig();
@@ -156,19 +154,19 @@ public class TestMetrics
try (HikariDataSource ds = newHikariDataSource()) {
ds.setMaximumPoolSize(1);
ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
+
MetricRegistry metricRegistry = new MetricRegistry();
HealthCheckRegistry healthRegistry = new HealthCheckRegistry();
-
+
try {
try (Connection connection = ds.getConnection()) {
// close immediately
}
-
+
// After the pool as started, we can only set them once...
ds.setMetricRegistry(metricRegistry);
ds.setHealthCheckRegistry(healthRegistry);
-
+
// and never again...
ds.setMetricRegistry(metricRegistry);
fail("Should not have been allowed to set registry after pool started");
@@ -192,19 +190,19 @@ public class TestMetrics
try (HikariDataSource ds = newHikariDataSource()) {
ds.setMaximumPoolSize(1);
ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
+
MetricRegistry metricRegistry = new MetricRegistry();
HealthCheckRegistry healthRegistry = new HealthCheckRegistry();
-
+
ds.setMetricRegistry(metricRegistry);
ds.setHealthCheckRegistry(healthRegistry);
-
+
// before the pool is started, we can set it any number of times...
ds.setMetricRegistry(metricRegistry);
ds.setHealthCheckRegistry(healthRegistry);
-
+
try (Connection connection = ds.getConnection()) {
-
+
// after the pool is started, we cannot set it any more
ds.setMetricRegistry(metricRegistry);
fail("Should not have been allowed to set registry after pool started");
@@ -221,15 +219,15 @@ public class TestMetrics
try (HikariDataSource ds = newHikariDataSource()) {
ds.setMaximumPoolSize(1);
ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
+
MetricRegistry metricRegistry = new MetricRegistry();
MetricsTrackerFactory metricsTrackerFactory = new CodahaleMetricsTrackerFactory(metricRegistry);
-
+
try (Connection connection = ds.getConnection()) {
-
+
// After the pool as started, we can only set them once...
ds.setMetricsTrackerFactory(metricsTrackerFactory);
-
+
// and never again...
ds.setMetricsTrackerFactory(metricsTrackerFactory);
fail("Should not have been allowed to set metricsTrackerFactory after pool started");
@@ -254,16 +252,16 @@ public class TestMetrics
try (HikariDataSource ds = newHikariDataSource()) {
ds.setMaximumPoolSize(1);
ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
+
MetricRegistry metricRegistry = new MetricRegistry();
-
+
// before the pool is started, we can set it any number of times using either setter
ds.setMetricRegistry(metricRegistry);
ds.setMetricRegistry(metricRegistry);
ds.setMetricRegistry(metricRegistry);
-
+
try (Connection connection = ds.getConnection()) {
-
+
// after the pool is started, we cannot set it any more
ds.setMetricRegistry(metricRegistry);
fail("Should not have been allowed to set registry after pool started");
@@ -280,17 +278,17 @@ public class TestMetrics
try (HikariDataSource ds = newHikariDataSource()) {
ds.setMaximumPoolSize(1);
ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
+
MetricRegistry metricRegistry = new MetricRegistry();
MetricsTrackerFactory metricsTrackerFactory = new CodahaleMetricsTrackerFactory(metricRegistry);
-
+
// before the pool is started, we can set it any number of times using either setter
ds.setMetricsTrackerFactory(metricsTrackerFactory);
ds.setMetricsTrackerFactory(metricsTrackerFactory);
ds.setMetricsTrackerFactory(metricsTrackerFactory);
-
+
try (Connection connection = ds.getConnection()) {
-
+
// after the pool is started, we cannot set it any more
ds.setMetricsTrackerFactory(metricsTrackerFactory);
fail("Should not have been allowed to set registry factory after pool started");
diff --git a/src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java b/src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java
index 06957b2..1c25a8f 100644
--- a/src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java
+++ b/src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java
@@ -16,9 +16,12 @@
package com.zaxxer.hikari.util;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
+import com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.DataInputStream;
import java.io.IOException;
@@ -26,15 +29,16 @@ import java.lang.ref.Reference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
import java.util.concurrent.CompletableFuture;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry;
+import static com.zaxxer.hikari.pool.TestElf.isJava9;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeTrue;
/**
* @author Brett Wooldridge
@@ -45,6 +49,8 @@ public class TomcatConcurrentBagLeakTest
@Test
public void testConcurrentBagForLeaks() throws Exception
{
+ assumeTrue(!isJava9());
+
ClassLoader cl = new FauxWebClassLoader();
Class<?> clazz = cl.loadClass(this.getClass().getName() + "$FauxWebContext");
Object fauxWebContext = clazz.newInstance();
@@ -60,6 +66,8 @@ public class TomcatConcurrentBagLeakTest
@Test
public void testConcurrentBagForLeaks2() throws Exception
{
+ assumeTrue(!isJava9());
+
ClassLoader cl = this.getClass().getClassLoader();
Class<?> clazz = cl.loadClass(this.getClass().getName() + "$FauxWebContext");
Object fauxWebContext = clazz.newInstance();
@@ -127,12 +135,15 @@ public class TomcatConcurrentBagLeakTest
}
}
+ @SuppressWarnings("unused")
public static class FauxWebContext
{
private static final Logger log = LoggerFactory.getLogger(FauxWebContext.class);
+ @SuppressWarnings("WeakerAccess")
public Exception failureException;
+ @SuppressWarnings("unused")
public void createConcurrentBag() throws InterruptedException
{
try (ConcurrentBag<PoolEntry> bag = new ConcurrentBag<>((x) -> CompletableFuture.completedFuture(Boolean.TRUE))) {
@@ -169,19 +180,19 @@ public class TomcatConcurrentBagLeakTest
Method expungeStaleEntriesMethod = tlmClass.getDeclaredMethod("expungeStaleEntries");
expungeStaleEntriesMethod.setAccessible(true);
- for (int i = 0; i < threads.length; i++) {
+ for (Thread thread : threads) {
Object threadLocalMap;
- if (threads[i] != null) {
+ if (thread != null) {
// Clear the first map
- threadLocalMap = threadLocalsField.get(threads[i]);
+ threadLocalMap = threadLocalsField.get(thread);
if (null != threadLocalMap) {
expungeStaleEntriesMethod.invoke(threadLocalMap);
checkThreadLocalMapForLeaks(threadLocalMap, tableField);
}
// Clear the second map
- threadLocalMap = inheritableThreadLocalsField.get(threads[i]);
+ threadLocalMap = inheritableThreadLocalsField.get(thread);
if (null != threadLocalMap) {
expungeStaleEntriesMethod.invoke(threadLocalMap);
checkThreadLocalMapForLeaks(threadLocalMap, tableField);
@@ -190,7 +201,7 @@ public class TomcatConcurrentBagLeakTest
}
}
catch (Throwable t) {
- log.warn("Failed to check for ThreadLocal references for web application [{}]", t);
+ log.warn("Failed to check for ThreadLocal references for web application [{}]", getContextName(), t);
failureException = new Exception();
}
}
@@ -212,8 +223,7 @@ public class TomcatConcurrentBagLeakTest
if (map != null) {
Object[] table = (Object[]) internalTableField.get(map);
if (table != null) {
- for (int j = 0; j < table.length; j++) {
- Object obj = table[j];
+ for (Object obj : table) {
if (obj != null) {
boolean keyLoadedByWebapp = false;
boolean valueLoadedByWebapp = false;
@@ -236,8 +246,7 @@ public class TomcatConcurrentBagLeakTest
args[1] = getPrettyClassName(key.getClass());
try {
args[2] = key.toString();
- }
- catch (Exception e) {
+ } catch (Exception e) {
log.warn("Unable to determine string representation of key of type [{}]", args[1], e);
args[2] = "Unknown";
}
@@ -246,8 +255,7 @@ public class TomcatConcurrentBagLeakTest
args[3] = getPrettyClassName(value.getClass());
try {
args[4] = value.toString();
- }
- catch (Exception e) {
+ } catch (Exception e) {
log.warn("webappClassLoader.checkThreadLocalsForLeaks.badValue {}", args[3], e);
args[4] = "Unknown";
}
@@ -255,21 +263,19 @@ public class TomcatConcurrentBagLeakTest
if (valueLoadedByWebapp) {
log.error("The web application [{}] created a ThreadLocal with key " +
- "(value [{}]) and a value of type [{}] (value [{}]) but failed to remove " +
- "it when the web application was stopped. Threads are going to be renewed " +
- "over time to try and avoid a probable memory leak.", args);
+ "(value [{}]) and a value of type [{}] (value [{}]) but failed to remove " +
+ "it when the web application was stopped. Threads are going to be renewed " +
+ "over time to try and avoid a probable memory leak.", args);
failureException = new Exception();
- }
- else if (value == null) {
+ } else if (value == null) {
log.debug("The web application [{}] created a ThreadLocal with key of type [{}] " +
- "(value [{}]). The ThreadLocal has been correctly set to null and the " +
- "key will be removed by GC.", args);
+ "(value [{}]). The ThreadLocal has been correctly set to null and the " +
+ "key will be removed by GC.", args);
failureException = new Exception();
- }
- else {
+ } else {
log.debug("The web application [{}] created a ThreadLocal with key of type [{}] " +
- "(value [{}]) and a value of type [{}] (value [{}]). Since keys are only " +
- "weakly held by the ThreadLocal Map this is not a memory leak.", args);
+ "(value [{}]) and a value of type [{}] (value [{}]). Since keys are only " +
+ "weakly held by the ThreadLocal Map this is not a memory leak.", args);
failureException = new Exception();
}
}
@@ -279,9 +285,45 @@ public class TomcatConcurrentBagLeakTest
}
}
- private boolean loadedByThisOrChild(Object key)
- {
- return key.getClass().getClassLoader() == this.getClass().getClassLoader();
+ /**
+ * @param o object to test, may be null
+ * @return <code>true</code> if o has been loaded by the current classloader
+ * or one of its descendants.
+ */
+ private boolean loadedByThisOrChild(Object o) {
+ if (o == null) {
+ return false;
+ }
+
+ Class<?> clazz;
+ if (o instanceof Class) {
+ clazz = (Class<?>) o;
+ } else {
+ clazz = o.getClass();
+ }
+
+ ClassLoader cl = clazz.getClassLoader();
+ while (cl != null) {
+ if (cl == this.getClass().getClassLoader()) {
+ return true;
+ }
+ cl = cl.getParent();
+ }
+
+ if (o instanceof Collection<?>) {
+ Iterator<?> iter = ((Collection<?>) o).iterator();
+ try {
+ while (iter.hasNext()) {
+ Object entry = iter.next();
+ if (loadedByThisOrChild(entry)) {
+ return true;
+ }
+ }
+ } catch (ConcurrentModificationException e) {
+ log.warn("Failed to check for ThreadLocal references for web application [{}]", getContextName(), e);
+ }
+ }
+ return false;
}
/*