From 808d040ea9d760bf468621984a3f2de865d35e7c Mon Sep 17 00:00:00 2001 From: Apollon Oikonomopoulos Date: Wed, 13 Sep 2017 00:26:37 +0300 Subject: New upstream version 2.7.1 --- src/main/java/com/zaxxer/hikari/HikariConfig.java | 161 +++++++++++++-------- .../java/com/zaxxer/hikari/HikariDataSource.java | 30 ++++ .../micrometer/MicrometerMetricsTracker.java | 115 +++++++++++++++ .../MicrometerMetricsTrackerFactory.java | 22 +++ .../metrics/prometheus/HikariCPCollector.java | 48 +++--- .../prometheus/PrometheusMetricsTracker.java | 94 +++++------- .../PrometheusMetricsTrackerFactory.java | 25 +++- .../java/com/zaxxer/hikari/pool/HikariPool.java | 115 ++++++++------- src/main/java/com/zaxxer/hikari/pool/PoolBase.java | 84 ++++++++--- .../java/com/zaxxer/hikari/pool/PoolEntry.java | 31 ++-- .../com/zaxxer/hikari/pool/ProxyConnection.java | 88 ++++++----- .../java/com/zaxxer/hikari/pool/ProxyFactory.java | 15 +- .../java/com/zaxxer/hikari/pool/ProxyLeakTask.java | 28 ++-- .../zaxxer/hikari/pool/ProxyLeakTaskFactory.java | 54 +++++++ .../zaxxer/hikari/pool/ProxyPreparedStatement.java | 4 +- .../com/zaxxer/hikari/pool/ProxyResultSet.java | 11 +- .../com/zaxxer/hikari/pool/ProxyStatement.java | 22 +-- .../java/com/zaxxer/hikari/util/ConcurrentBag.java | 32 ++-- .../com/zaxxer/hikari/util/DriverDataSource.java | 25 +++- .../hikari/util/QueuedSequenceSynchronizer.java | 137 ------------------ src/main/java/com/zaxxer/hikari/util/Sequence.java | 71 --------- .../micrometer/MicrometerMetricsTrackerTest.java | 39 +++++ .../metrics/prometheus/HikariCPCollectorTest.java | 11 +- .../prometheus/PrometheusMetricsTrackerTest.java | 44 +++++- .../zaxxer/hikari/mocks/StubPreparedStatement.java | 2 +- .../com/zaxxer/hikari/mocks/StubStatement.java | 11 ++ .../com/zaxxer/hikari/osgi/OSGiBundleTest.java | 118 +++++++++------ .../pool/ConnectionPoolSizeVsThreadsTest.java | 4 +- .../hikari/pool/ConnectionRaceConditionTest.java | 2 +- .../zaxxer/hikari/pool/HouseKeeperCleanupTest.java | 2 +- .../com/zaxxer/hikari/pool/MetricsTrackerTest.java | 86 +++++++++++ src/test/java/com/zaxxer/hikari/pool/MiscTest.java | 7 +- .../zaxxer/hikari/pool/SaturatedPoolTest830.java | 156 ++++++++++++++++++++ .../hikari/pool/TestConnectionTimeoutRetry.java | 5 +- src/test/java/com/zaxxer/hikari/pool/TestElf.java | 23 +-- src/test/java/com/zaxxer/hikari/pool/TestJNDI.java | 27 ++++ .../java/com/zaxxer/hikari/pool/TestMBean.java | 93 +++++++++--- .../java/com/zaxxer/hikari/pool/TestMetrics.java | 46 +++--- .../hikari/util/TomcatConcurrentBagLeakTest.java | 112 +++++++++----- 39 files changed, 1310 insertions(+), 690 deletions(-) create mode 100644 src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTracker.java create mode 100644 src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerFactory.java create mode 100644 src/main/java/com/zaxxer/hikari/pool/ProxyLeakTaskFactory.java delete mode 100644 src/main/java/com/zaxxer/hikari/util/QueuedSequenceSynchronizer.java delete mode 100644 src/main/java/com/zaxxer/hikari/util/Sequence.java create mode 100755 src/test/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerTest.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/MetricsTrackerTest.java create mode 100644 src/test/java/com/zaxxer/hikari/pool/SaturatedPoolTest830.java (limited to 'src') 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 Connection.isValid() 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. * - * 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,12 +773,28 @@ 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 Connection class, eg. @@ -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$2"); + } else if (prop.contains("password")) { value = ""; } @@ -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 @@ -263,6 +263,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 @@ -281,7 +303,11 @@ public class HikariDataSource extends HikariConfig implements DataSource, Closea /** * Suspend allocation of connections from the pool. All callers to getConnection() * will block indefinitely until resumePool() 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 labelNames; - private final List labelValues; - - HikariCPCollector(String poolName, PoolStats poolStats) { - this.poolStats = poolStats; - this.labelNames = Collections.singletonList("pool"); - this.labelValues = Collections.singletonList(poolName); - } + + private static final List LABEL_NAMES = Collections.singletonList("pool"); + + private final Map poolStatsMap = new ConcurrentHashMap<>(); @Override public List 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 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 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; - /** *
{@code
  * HikariConfig config = new HikariConfig();
  * config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
  * }
*/ -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 addConnectionQueue; private final ThreadPoolExecutor addConnectionExecutor; private final ThreadPoolExecutor closeConnectionExecutor; private final ConcurrentBag 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 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 { - 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 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() {} @@ -675,6 +713,12 @@ abstract class PoolBase tracker.recordConnectionCreatedMillis(connectionCreatedMillis); } + @Override + public void recordBorrowTimeoutStats(long startTime) + { + tracker.recordConnectionAcquiredNanos(elapsedNanos(startTime)); + } + @Override public void recordBorrowStats(final PoolEntry poolEntry, final long startTime) { 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 stateUpdater; - static final Comparator 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() { - @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 ERROR_STATES; private static final Set 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 trackStatement(final T statement) + private synchronized 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(); } } @@ -344,6 +352,14 @@ public abstract class ProxyConnection implements Connection return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, columnNames))); } + /** {@inheritDoc} */ + @Override + public DatabaseMetaData getMetaData() throws SQLException + { + markCommitStateDirty(); + return delegate.getMetaData(); + } + /** {@inheritDoc} */ @Override public void commit() throws SQLException @@ -417,6 +433,15 @@ public abstract class ProxyConnection implements Connection dirtyBits |= DIRTY_BIT_NETTIMEOUT; } + /** {@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 @@ -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 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; @@ -44,36 +42,30 @@ class ProxyLeakTask implements Runnable static { 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 implements AutoCloseab private final SynchronousQueue 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 implements AutoCloseab int getState(); } - public static interface IBagStateListener + public interface IBagStateListener { - Future addBagItem(int waiting); + void addBagItem(int waiting); } /** @@ -147,7 +150,7 @@ public class ConcurrentBag implements AutoCloseab } listener.addBagItem(waiting); - + timeout = timeUnit.toNanos(timeout); do { final long start = currentTime(); @@ -179,11 +182,16 @@ public class ConcurrentBag 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 threadLocalList = threadList.get(); @@ -254,7 +262,9 @@ public class ConcurrentBag implements AutoCloseab */ public List values(final int state) { - return sharedList.stream().filter(e -> e.getState() == state).collect(Collectors.toList()); + final List 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. - *

- * When a shared resource becomes available the {@link #signal()} method should - * be called unconditionally. - *

- * A thread wishing to acquire a shared resource should:
- *

    - *
  • Obtain the current sequence from the {@link #currentSequence()} method
  • - *
  • Call {@link #waitUntilSequenceExceeded(long, long)} with that sequence.
  • - *
  • Upon receiving a true 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.
  • - *
  • If the shared resource cannot be acquired, the thread should again call - * {@link #waitUntilSequenceExceeded(long, long)} with the previously obtained sequence.
  • - *
  • If false is received from {@link #waitUntilSequenceExceeded(long, long)} - * then a timeout has occurred.
  • - *
- *

- * 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 Java Spec - * @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 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 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) 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 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 true 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; } /* -- cgit v1.2.3