Skip to content
Commits on Source (8)
......@@ -6,3 +6,4 @@ Database : PostgreSQL|MySQL|...
Driver version : x.x.x
```
-----------------------------------------------------------------------------------------
*Have you searched the CLOSED issues already? How about checking [stackoverflow](https://stackoverflow.com/search?q=HikariCP)?*
......@@ -6,7 +6,6 @@ dist:
before_script:
- if [[ "x$JDK" == *'x9'* ]]; then export MAVEN_SKIP_RC=true; fi
- if [[ "x$JDK" != *'x9'* ]]; then export COVERALLS=coveralls:report; fi
script:
- export JDK8_HOME=$(jdk_switcher home oraclejdk8)
......@@ -32,5 +31,7 @@ matrix:
install: /bin/true
script:
- mvn package -Dskip.unit.tests=true -Dmaven.javadoc.skip=true -V -B
- mvn test $COVERALLS -V -P coverage
- mvn package -Dmaven.javadoc.skip=true -V -B
after_success:
- bash <(curl -s https://codecov.io/bash)
HikariCP Changes
Changes in 2.7.7
* fixed issue whereby configuration through the HikariConfigMXBean could not be altered due
to the sealed configuration change introduced in 2.7.5.
Changes in 2.7.6
* issue 1064 fixed regression where HikariConfig.copyStateTo() propagated the "sealed" status
of the source configuration to the target configuration -- preventing further changes.
Changes in 2.7.5
* issue 1061/pull 1062 fixed compatibility issue with requery.io caused by HikariCP's
proxied Statement class returning a driver-native ResultSet instance from getGeneratedKeys()
instead of returning a HikariCP proxied ResultSet.
* pull 1058 enable quantiles on Prometheus metrics.
* pull 1055 fixed incorrect JavaDoc for HikariConfigMXBean.getMinimumIdle() method.
* issue 1045/pull 1047 added Automatic-Module-Name to jar manifest to ensure that the Java 8
library plays well with the Java 9 module system.
* introduced the concept of a "sealed" configuration. Once a pool is started, attempts to
alter its configuration outside of the HikariConfigMXBean will result in an IllegalStateException.
Changes in 2.7.4
* pull 1026 added support for SQL Server's specific isolation level (SNAPSHOT).
* issue 926/pull 1022 HikariJNDIFactory should not throw a NamingException or else
cascading to other object factories cannot occur.
Changes in 2.7.3
* issue 1003 added PostgreSQL SQL State 0A000 to list of unrecoverable states calling
for eviction.
* pull 1002 updated micrometer support due to API changes in their release candidate.
Changes in 2.7.2
* issue 983 fix logic that determines how many idle connections can be removed, without
violating the minimumIdle contract.
* pull 987 add thread name to leak detection messages.
* issue 982 fix classloader order, try the ThreadContext classloader before other
classloaders.
* pull 977 log better messages when connection is evicted.
* fallback to four digit random pool suffix when SecurityManager prevents writing
to system properties for the purpose of JVM-wide unique pool identifiers.
Changes in 2.7.1
* issue 968 Wrong label order in MicrometerMetricsTracker for the connection usage
......@@ -9,6 +64,14 @@ Changes in 2.7.1
intended to cause parkNanos() to be called every 256 iterations. Thanks to @ztkmkoo
for finding this.
Changes in 2.4.13
* backport more efficient contention handling in ConcurrentBag.requite from 2.6.x
branch.
* issue 955 fix possible race condition when Statements are closed on different
threads from which they were created.
Changes in 2.7.0
* added support for micrometer metrics (currently Alpha-level support).
......
......@@ -2,7 +2,6 @@
[![][Build Status img]][Build Status]
[![][Coverage Status img]][Coverage Status]
[![][Dependency Status img]][Dependency Status]
[![][license img]][license]
[![][Maven Central img]][Maven Central]
[![][Javadocs img]][Javadocs]
......@@ -14,20 +13,12 @@ Fast, simple, reliable. HikariCP is a "zero-overhead" production ready JDBC con
----------------------------------------------------
_Java 8 maven artifact:_
_Java 8/9 maven artifact:_
```xml
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.7.0</version>
</dependency>
```
_Java 9 Early Access maven artifact:_
```xml
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP-java9ea</artifactId>
<version>2.6.1</version>
<version>2.7.7</version>
</dependency>
```
_Java 7 maven artifact (*maintenance mode*):_
......@@ -35,7 +26,7 @@ _Java 7 maven artifact (*maintenance mode*):_
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP-java7</artifactId>
<version>2.4.12</version>
<version>2.4.13</version>
</dependency>
```
_Java 6 maven artifact (*maintenance mode*):_
......@@ -108,7 +99,7 @@ HikariCP comes with *sane* defaults that perform well in most deployments withou
<sup>&#128206;</sup>&nbsp;*HikariCP uses milliseconds for all time values.*
&#128680;&nbsp;HikariCP relies on accurate timers for both performance and reliability. It is *imperative* that your server is synchronized with a time-source such as an NTP server. *Especially* if your server is running within a virtual machine. Why? [Read more here](https://dba.stackexchange.com/questions/171002/choice-of-connection-pooling-library-for-vm-deploys/171020). **Do not rely on hypervisor settings to "synchronize" the clock of the virtual machine. Configure time-source synchronization inside the virtual machine.** If you come asking for support on an issue that turns out to be caused by lack time synchronization, you will be taunted publicly on Twitter.
&#128680;&nbsp;HikariCP relies on accurate timers for both performance and reliability. It is *imperative* that your server is synchronized with a time-source such as an NTP server. *Especially* if your server is running within a virtual machine. Why? [Read more here](https://dba.stackexchange.com/a/171020). **Do not rely on hypervisor settings to "synchronize" the clock of the virtual machine. Configure time-source synchronization inside the virtual machine.** If you come asking for support on an issue that turns out to be caused by lack time synchronization, you will be taunted publicly on Twitter.
##### Essentials
......@@ -171,14 +162,16 @@ This property controls the maximum amount of time that a connection is allowed t
pool. **This setting only applies when ``minimumIdle`` is defined to be less than ``maximumPoolSize``.**
Whether a connection is retired as idle or not is subject to a maximum variation of +30
seconds, and average variation of +15 seconds. A connection will never be retired as idle *before*
this timeout. A value of 0 means that idle connections are never removed from the pool. The minimum
this timeout. Once the pool reaches ``minimumIdle`` connections, connections will no longer be
retired, even if idle. A value of 0 means that idle connections are never removed from the pool. The minimum
allowed value is 10000ms (10 seconds).
*Default: 600000 (10 minutes)*
&#8986;``maxLifetime``<br/>
This property controls the maximum lifetime of a connection in the pool. An in-use connection will
never be retired, only when it is closed will it then be removed. **We strongly recommend setting
this value, and it should be at least 30 seconds less than any database or infrastructure imposed
never be retired, only when it is closed will it then be removed. On a connection-by-connection
basis, minor negative attenuation is applied to avoid mass-extinction in the pool. **We strongly recommend
setting this value, and it should be at least 30 seconds less than any database or infrastructure imposed
connection time limit.** A value of 0 indicates no maximum lifetime (infinite lifetime), subject of
course to the ``idleTimeout`` setting.
*Default: 1800000 (30 minutes)*
......@@ -311,6 +304,11 @@ frameworks. When this property is specified, the ``dataSourceClassName`` propert
DataSource-specific properties will be ignored.
*Default: none*
&#128288;``schema``<br/>
This property sets the default *schema* for databases that support the concept of schemas.
If this property is not specified, the default schema defined by the JDBC driver is used.
*Default: driver default*
&#10145;``threadFactory``<br/>
This property is only available via programmatic configuration or IoC container. This property
allows you to set the instance of the ``java.util.concurrent.ThreadFactory`` that will be used
......@@ -357,8 +355,9 @@ and will negatively impact your application performance compared to driver-provi
Like Statement caching, most major database vendors support statement logging through
properties of their own driver. This includes Oracle, MySQL, Derby, MSSQL, and others. Some
even support slow query logging. For those few databases that do not support it, [log4jdbc](https://github.com/arthurblake/log4jdbc) or [jdbcdslog-exp](https://code.google.com/p/jdbcdslog-exp/) are
good options.
even support slow query logging. For those few databases that do not support it, several options are available.
We have received [a report that p6spy works well](https://github.com/brettwooldridge/HikariCP/issues/57#issuecomment-354647631),
and also note the availability of [log4jdbc](https://github.com/arthurblake/log4jdbc) and [jdbcdslog-exp](https://code.google.com/p/jdbcdslog-exp/).
----------------------------------------------------
......@@ -419,7 +418,7 @@ location of a properties file. If you intend to use this option, construct a ``
instance using the default constructor and the properties file will be loaded.
### Performance Tips
[MySQL Performnace Tips](https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration)
[MySQL Performance Tips](https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration)
### Popular DataSource Class Names
......@@ -434,14 +433,14 @@ Here is a list of JDBC *DataSource* classes for popular databases:
| Database | Driver | *DataSource* class |
|:---------------- |:------------ |:-------------------|
| Apache Derby | Derby | org.apache.derby.jdbc.ClientDataSource |
| Firebird | Jaybird | org.firebirdsql.pool.FBSimpleDataSource |
| Firebird | Jaybird | org.firebirdsql.ds.FBSimpleDataSource |
| H2 | H2 | org.h2.jdbcx.JdbcDataSource |
| HSQLDB | HSQLDB | org.hsqldb.jdbc.JDBCDataSource |
| IBM DB2 | IBM JCC | com.ibm.db2.jcc.DB2SimpleDataSource |
| IBM Informix | IBM Informix | com.informix.jdbcx.IfxDataSource |
| MS SQL Server | Microsoft | com.microsoft.sqlserver.jdbc.SQLServerDataSource |
| ~~MySQL~~ | Connector/J | ~~com.mysql.jdbc.jdbc2.optional.MysqlDataSource~~ |
| MySQL/MariaDB | MariaDB | org.mariadb.jdbc.MySQLDataSource |
| MariaDB | MariaDB | org.mariadb.jdbc.MariaDbDataSource |
| Oracle | Oracle | oracle.jdbc.pool.OracleDataSource |
| OrientDB | OrientDB | com.orientechnologies.orient.jdbc.OrientDataSource |
| PostgreSQL | pgjdbc-ng | com.impossibl.postgres.jdbc.PGDataSource |
......@@ -486,10 +485,14 @@ Don't forget the [Wiki](https://github.com/brettwooldridge/HikariCP/wiki) for ad
&#8658; slf4j library<br/>
### Sponsors
High-performance projects can never have too many tools! We would like to thank the following companies:
Thanks to [ej-technologies](https://www.ej-technologies.com) for their excellent all-in-one profiler, [JProfiler](https://www.ej-technologies.com/products/jprofiler/overview.html).
YourKit supports open source projects with its full-featured Java Profiler. Click the YourKit logo below to learn more.<br/>
[![](https://github.com/brettwooldridge/HikariCP/wiki/yklogo.png)](http://www.yourkit.com/java/profiler/index.jsp)<br/>
### Contributions
Please perform changes and submit pull requests from the ``dev`` branch instead of ``master``. Please set your editor to use spaces instead of tabs, and adhere to the apparent style of the code you are editing. The ``dev`` branch is always more "current" than the ``master`` if you are looking to live life on the edge.
......@@ -497,11 +500,8 @@ Please perform changes and submit pull requests from the ``dev`` branch instead
[Build Status]:https://travis-ci.org/brettwooldridge/HikariCP
[Build Status img]:https://travis-ci.org/brettwooldridge/HikariCP.svg?branch=dev
[Coverage Status]:https://coveralls.io/r/brettwooldridge/HikariCP?branch=dev
[Coverage Status img]:https://coveralls.io/repos/brettwooldridge/HikariCP/badge.svg?branch=dev
[Dependency Status]:https://www.versioneye.com/user/projects/551ce51c3661f1bee50004e0
[Dependency Status img]:https://www.versioneye.com/user/projects/551ce51c3661f1bee50004e0/badge.svg?style=flat
[Coverage Status]:https://codecov.io/gh/brettwooldridge/HikariCP
[Coverage Status img]:https://codecov.io/gh/brettwooldridge/HikariCP/branch/dev/graph/badge.svg
[license]:LICENSE
[license img]:https://img.shields.io/badge/license-Apache%202-blue.svg
......
codecov:
notify:
require_ci_to_pass: yes
coverage:
precision: 2
round: down
range: "50..80"
status:
project: yes
patch: yes
changes: no
parsers:
gcov:
branch_detection:
conditional: yes
loop: yes
method: no
macro: no
comment:
layout: "reach, diff, flags, files, footer"
behavior: default
require_changes: no
......@@ -65,8 +65,10 @@ Where ``150`` is the connection establishment time, ``<pool>`` is one of [*hikar
<sup>* Note that the times provided in the raw data is the number of microseconds (μs) since the start of the test. For graphing purposes, raw data for each pool was trimmed such that the first entry has 0 requests enqueued, and the last entry has all connections completed. </sup>
--------------------
#### Apache DBCP vs HikariCP
In case you missed the *time-scale* in the graphs above, here is a properly scaled comparable; Apache DBCP on top, HikariCP on the bottom.
### Apache DBCP vs HikariCP
:point_right: In case you missed the *time-scale* in the graphs above, here is a properly scaled comparable.
Apache DBCP on *top*, HikariCP on the *bottom*.
[![](https://github.com/brettwooldridge/HikariCP/wiki/Spike-Compare.png)](https://github.com/brettwooldridge/HikariCP/wiki/Spike-Compare.png)
......@@ -85,7 +87,7 @@ Consider this hypothetical scenario:
There is a pool with five connections in-use, and zero idle (available) connections. Then, a new thread
comes in requesting a connection.
```
"How the the prime directive apply in this case?" We'll answer with a question of our own:
"How does the prime directive apply in this case?" We'll answer with a question of our own:
> If the thread is directed to create a new connection, and that connection takes 150ms to establish, what happens if one of the five in-use connections is returned to the pool?
......
......@@ -8,27 +8,28 @@
<artifact.classifier />
<felix.bundle.plugin.version>3.3.0</felix.bundle.plugin.version>
<felix.version>5.6.2</felix.version>
<felix.version>5.6.8</felix.version>
<hibernate.version>5.2.10.Final</hibernate.version>
<javassist.version>3.22.0-CR1</javassist.version>
<jndi.version>0.11.4.1</jndi.version>
<maven.release.version>2.5.3</maven.release.version>
<metrics.version>3.2.2</metrics.version>
<micrometer.version>0.10.0.RELEASE</micrometer.version>
<simpleclient.version>0.0.22</simpleclient.version>
<mockito.version>2.8.9</mockito.version>
<metrics.version>3.2.4</metrics.version>
<micrometer.version>1.0.0</micrometer.version>
<simpleclient.version>0.0.26</simpleclient.version>
<mockito.version>2.10.0</mockito.version>
<pax.exam.version>4.11.0</pax.exam.version>
<pax.url.version>2.5.2</pax.url.version>
<postgresql.version>42.1.4</postgresql.version>
<slf4j.version>1.7.25</slf4j.version>
<log4j.version>2.8.2</log4j.version>
<commons.csv.version>1.4</commons.csv.version>
<h2.version>1.4.195</h2.version>
<log4j.version>2.9.0</log4j.version>
<commons.csv.version>1.5</commons.csv.version>
<h2.version>1.4.196</h2.version>
<junit.version>4.12</junit.version>
</properties>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.7.1</version>
<version>2.7.9</version>
<packaging>bundle</packaging>
<name>HikariCP</name>
......@@ -44,7 +45,7 @@
<connection>scm:git:git@github.com:brettwooldridge/HikariCP.git</connection>
<developerConnection>scm:git:git@github.com:brettwooldridge/HikariCP.git</developerConnection>
<url>git@github.com:brettwooldridge/HikariCP.git</url>
<tag>HikariCP-2.7.1</tag>
<tag>HikariCP-2.7.9</tag>
</scm>
<licenses>
......@@ -135,7 +136,7 @@
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.1.4.jre7</version>
<version>${postgresql.version}</version>
<scope>test</scope>
</dependency>
<dependency>
......@@ -268,7 +269,6 @@
<executions>
<!-- Prepares the property pointing to the JaCoCo runtime agent which is passed as VM argument when Maven the Surefire plugin is executed. -->
<execution>
<id>pre-unit-test</id>
<goals>
<goal>prepare-agent</goal>
</goals>
......@@ -286,7 +286,7 @@
</execution>
<!-- Ensures that the code coverage report for unit tests is created after unit tests have been run. -->
<execution>
<id>post-unit-test</id>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
......@@ -294,8 +294,6 @@
<configuration>
<!-- Sets the path to the file which contains the execution data. -->
<dataFile>${project.build.directory}/coverage-reports/jacoco.exec</dataFile>
<!-- Sets the output directory for the code coverage report. -->
<outputDirectory>${project.reporting.outputDirectory}/jacoco</outputDirectory>
<excludes>
<exclude>**/com/zaxxer/hikari/pool/HikariProxy*</exclude>
<exclude>**/com/zaxxer/hikari/metrics/**</exclude>
......@@ -313,6 +311,7 @@
<configuration>
<classifier>${artifact.classifier}</classifier>
<instructions>
<Automatic-Module-Name>com.zaxxer.hikari</Automatic-Module-Name>
<Bundle-Name>HikariCP</Bundle-Name>
<Export-Package>
com.zaxxer.hikari,
......@@ -362,11 +361,12 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<version>3.7.0</version>
<extensions>true</extensions>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArgs>-Xlint</compilerArgs>
</configuration>
</plugin>
......@@ -454,29 +454,6 @@
</properties>
</profile>
<profile>
<id>coverage</id>
<build>
<plugins>
<plugin>
<groupId>org.eluder.coveralls</groupId>
<artifactId>coveralls-maven-plugin</artifactId>
<version>4.3.0</version>
<executions>
<execution>
<id>coveralls</id>
<phase>verify</phase>
<goals>
<goal>jacoco</goal>
</goals>
<inherited>false</inherited>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>release-sign-artifacts</id>
<activation>
......
......@@ -112,12 +112,9 @@ public interface HikariConfigMXBean
void setMaxLifetime(long maxLifetimeMs);
/**
* The property controls the maximum size that the pool is allowed to reach, including both idle and in-use
* connections. Basically this value will determine the maximum number of actual connections to the database
* backend.
* <p>
* When the pool reaches this size, and no idle connections are available, calls to getConnection() will
* block for up to connectionTimeout milliseconds before timing out.
* The property controls the minimum number of idle connections that HikariCP tries to maintain in the pool,
* including both idle and in-use connections. If the idle connections dip below this value, HikariCP will
* make a best effort to restore them quickly and efficiently.
*
* @return the minimum number of connections in the pool
*/
......
......@@ -16,6 +16,13 @@
package com.zaxxer.hikari;
import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
import com.zaxxer.hikari.pool.HikariPool;
import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.io.Closeable;
import java.io.PrintWriter;
import java.sql.Connection;
......@@ -23,14 +30,7 @@ import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
import com.zaxxer.hikari.pool.HikariPool;
import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException;
import static com.zaxxer.hikari.pool.HikariPool.POOL_SHUTDOWN;
/**
* The HikariCP pooled DataSource.
......@@ -47,10 +47,14 @@ public class HikariDataSource extends HikariConfig implements DataSource, Closea
private volatile HikariPool pool;
/**
* Default constructor. Setters be used to configure the pool. Using
* Default constructor. Setters are used to configure the pool. Using
* this constructor vs. {@link #HikariDataSource(HikariConfig)} will
* result in {@link #getConnection()} performance that is slightly lower
* due to lazy initialization checks.
*
* The first call to {@link #getConnection()} starts the pool. Once the pool
* is started, the configuration is "sealed" and no further configuration
* changes are possible -- except via {@link HikariConfigMXBean} methods.
*/
public HikariDataSource()
{
......@@ -59,20 +63,31 @@ public class HikariDataSource extends HikariConfig implements DataSource, Closea
}
/**
* Construct a HikariDataSource with the specified configuration.
* Construct a HikariDataSource with the specified configuration. The
* {@link HikariConfig} is copied and the pool is started by invoking this
* constructor.
*
* The {@link HikariConfig} can be modified without affecting the HikariDataSource
* and used to initialize another HikariDataSource instance.
*
* @param configuration a HikariConfig instance
*/
public HikariDataSource(HikariConfig configuration)
{
configuration.validate();
configuration.copyState(this);
configuration.copyStateTo(this);
LOGGER.info("{} - Starting...", configuration.getPoolName());
pool = fastPathPool = new HikariPool(this);
LOGGER.info("{} - Start completed.", configuration.getPoolName());
this.seal();
}
// ***********************************************************************
// DataSource methods
// ***********************************************************************
/** {@inheritDoc} */
@Override
public Connection getConnection() throws SQLException
......@@ -95,6 +110,7 @@ public class HikariDataSource extends HikariConfig implements DataSource, Closea
LOGGER.info("{} - Starting...", getPoolName());
try {
pool = result = new HikariPool(this);
this.seal();
}
catch (PoolInitializationException pie) {
if (pie.getCause() instanceof SQLException) {
......@@ -209,6 +225,10 @@ public class HikariDataSource extends HikariConfig implements DataSource, Closea
return false;
}
// ***********************************************************************
// HikariConfigMXBean methods
// ***********************************************************************
/** {@inheritDoc} */
@Override
public void setMetricRegistry(Object metricRegistry)
......@@ -263,6 +283,20 @@ public class HikariDataSource extends HikariConfig implements DataSource, Closea
}
}
// ***********************************************************************
// HikariCP-specific methods
// ***********************************************************************
/**
* Returns {@code true} if the pool as been started and is not suspended or shutdown.
*
* @return {@code true} if the pool as been started and is not suspended or shutdown.
*/
public boolean isRunning()
{
return pool != null && pool.poolState != POOL_SHUTDOWN;
}
/**
* 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,
......
......@@ -43,15 +43,8 @@ public class HikariJNDIFactory implements ObjectFactory
synchronized public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception
{
// We only know how to deal with <code>javax.naming.Reference</code> that specify a class name of "javax.sql.DataSource"
if (!(obj instanceof Reference)) {
return null;
}
if (obj instanceof Reference && "javax.sql.DataSource".equals(((Reference) obj).getClassName())) {
Reference ref = (Reference) obj;
if (!"javax.sql.DataSource".equals(ref.getClassName())) {
throw new NamingException(ref.getClassName() + " is not a valid class name/type for this JNDI factory.");
}
Set<String> hikariPropSet = PropertyElf.getPropertyNames(HikariConfig.class);
Properties properties = new Properties();
......@@ -63,9 +56,10 @@ public class HikariJNDIFactory implements ObjectFactory
properties.setProperty(type, element.getContent().toString());
}
}
return createDataSource(properties, nameCtx);
}
return null;
}
private DataSource createDataSource(final Properties properties, final Context context) throws NamingException
{
......
File mode changed from 100755 to 100644
......@@ -34,6 +34,8 @@ public abstract class PoolStats
protected volatile int idleConnections;
protected volatile int activeConnections;
protected volatile int pendingThreads;
protected volatile int maxConnections;
protected volatile int minConnections;
public PoolStats(final long timeoutMs)
{
......@@ -77,6 +79,22 @@ public abstract class PoolStats
return pendingThreads;
}
public int getMaxConnections() {
if (shouldLoad()) {
update();
}
return maxConnections;
}
public int getMinConnections() {
if (shouldLoad()) {
update();
}
return minConnections;
}
protected abstract void update();
private boolean shouldLoad()
......
......@@ -44,6 +44,8 @@ public final class CodaHaleMetricsTracker implements IMetricsTracker
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 static final String METRIC_NAME_MAX_CONNECTIONS = "MaxConnections";
private static final String METRIC_NAME_MIN_CONNECTIONS = "MinConnections";
public CodaHaleMetricsTracker(final String poolName, final PoolStats poolStats, final MetricRegistry registry)
{
......@@ -85,6 +87,22 @@ public final class CodaHaleMetricsTracker implements IMetricsTracker
return poolStats.getPendingThreads();
}
});
registry.register(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_MAX_CONNECTIONS),
new Gauge<Integer>() {
@Override
public Integer getValue() {
return poolStats.getMaxConnections();
}
});
registry.register(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_MIN_CONNECTIONS),
new Gauge<Integer>() {
@Override
public Integer getValue() {
return poolStats.getMinConnections();
}
});
}
/** {@inheritDoc} */
......@@ -99,6 +117,8 @@ public final class CodaHaleMetricsTracker implements IMetricsTracker
registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_IDLE_CONNECTIONS));
registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_ACTIVE_CONNECTIONS));
registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_PENDING_CONNECTIONS));
registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_MAX_CONNECTIONS));
registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_MIN_CONNECTIONS));
}
/** {@inheritDoc} */
......
......@@ -2,33 +2,56 @@ 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.Counter;
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;
/**
* {@link IMetricsTracker Metrics tracker} for Micrometer.
* HikariCP metrics can be configured in your application by applying a
* {@link io.micrometer.core.instrument.config.MeterFilter MeterFilter} to metrics starting with
* {@link #HIKARI_METRIC_NAME_PREFIX}. For example, to configure client-side calculated percentiles:
*
* <blockquote><pre>
* new MeterFilter() {
* &#064;Override
* public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) {
* if (id.getName().startsWith(MicrometerMetricsTracker.HIKARI_METRIC_NAME_PREFIX)) {
* return DistributionStatisticConfig.builder()
* .percentiles(0.5, 0.95)
* .build()
* .merge(config);
* }
* return config;
* }
* };
* </pre></blockquote>
*/
public class MicrometerMetricsTracker implements IMetricsTracker
{
/** Prefix used for all HikariCP metric names. */
public static final String HIKARI_METRIC_NAME_PREFIX = "hikaricp";
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 static final String METRIC_NAME_WAIT = HIKARI_METRIC_NAME_PREFIX + ".connections.acquire";
private static final String METRIC_NAME_USAGE = HIKARI_METRIC_NAME_PREFIX + ".connections.usage";
private static final String METRIC_NAME_CONNECT = HIKARI_METRIC_NAME_PREFIX + ".connections.creation";
private static final String METRIC_NAME_TIMEOUT_RATE = HIKARI_METRIC_NAME_PREFIX + ".connections.timeout";
private static final String METRIC_NAME_TOTAL_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections";
private static final String METRIC_NAME_IDLE_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.idle";
private static final String METRIC_NAME_ACTIVE_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.active";
private static final String METRIC_NAME_PENDING_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.pending";
private static final String METRIC_NAME_MAX_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.max";
private static final String METRIC_NAME_MIN_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.min";
private final Timer connectionObtainTimer;
private final DistributionSummary connectionTimeoutMeter;
private final DistributionSummary connectionUsage;
private final DistributionSummary connectionCreation;
private final Counter connectionTimeoutCounter;
private final Timer connectionUsage;
private final Timer connectionCreation;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
private final Gauge totalConnectionGauge;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
......@@ -37,54 +60,67 @@ public class MicrometerMetricsTracker implements IMetricsTracker
private final Gauge activeConnectionGauge;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
private final Gauge pendingConnectionGauge;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
private final Gauge maxConnectionGauge;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
private final Gauge minConnectionGauge;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
private final PoolStats poolStats;
MicrometerMetricsTracker(final String poolName, final PoolStats poolStats, final MeterRegistry meterRegistry)
{
this.connectionObtainTimer = meterRegistry
.timerBuilder(METRIC_NAME_WAIT)
this.poolStats = poolStats;
this.connectionObtainTimer = Timer.builder(METRIC_NAME_WAIT)
.description("Connection acquire time")
.tags(METRIC_CATEGORY, poolName)
.create();
.register(meterRegistry);
this.connectionCreation = meterRegistry
.summaryBuilder(METRIC_NAME_CONNECT)
this.connectionCreation = Timer.builder(METRIC_NAME_CONNECT)
.description("Connection creation time")
.tags(METRIC_CATEGORY, poolName)
.quantiles(WindowSketchQuantiles.quantiles(0.5, 0.95).create())
.histogram(buckets(linear(0, 10, 20), TimeUnit.MILLISECONDS))
.create();
.register(meterRegistry);
this.connectionUsage = meterRegistry
.summaryBuilder(METRIC_NAME_USAGE)
this.connectionUsage = Timer.builder(METRIC_NAME_USAGE)
.description("Connection usage time")
.tags(METRIC_CATEGORY, poolName)
.quantiles(WindowSketchQuantiles.quantiles(0.5, 0.95).create())
.histogram(buckets(linear(0, 10, 20), TimeUnit.MILLISECONDS))
.create();
.register(meterRegistry);
this.connectionTimeoutMeter = meterRegistry
.summaryBuilder(METRIC_NAME_TIMEOUT_RATE)
this.connectionTimeoutCounter = Counter.builder(METRIC_NAME_TIMEOUT_RATE)
.description("Connection timeout total count")
.tags(METRIC_CATEGORY, poolName)
.quantiles(WindowSketchQuantiles.quantiles(0.5, 0.95).create())
.histogram(buckets(linear(0, 10, 20), TimeUnit.MILLISECONDS))
.create();
.register(meterRegistry);
this.totalConnectionGauge = meterRegistry
.gaugeBuilder(METRIC_NAME_TOTAL_CONNECTIONS, Integer.class, (i) -> poolStats.getTotalConnections())
this.totalConnectionGauge = Gauge.builder(METRIC_NAME_TOTAL_CONNECTIONS, poolStats, PoolStats::getTotalConnections)
.description("Total connections")
.tags(METRIC_CATEGORY, poolName)
.create();
.register(meterRegistry);
this.idleConnectionGauge = meterRegistry
.gaugeBuilder(METRIC_NAME_IDLE_CONNECTIONS, Integer.class, (i) -> poolStats.getIdleConnections())
this.idleConnectionGauge = Gauge.builder(METRIC_NAME_IDLE_CONNECTIONS, poolStats, PoolStats::getIdleConnections)
.description("Idle connections")
.tags(METRIC_CATEGORY, poolName)
.create();
.register(meterRegistry);
this.activeConnectionGauge = meterRegistry
.gaugeBuilder(METRIC_NAME_ACTIVE_CONNECTIONS, Integer.class, (i) -> poolStats.getActiveConnections())
this.activeConnectionGauge = Gauge.builder(METRIC_NAME_ACTIVE_CONNECTIONS, poolStats, PoolStats::getActiveConnections)
.description("Active connections")
.tags(METRIC_CATEGORY, poolName)
.create();
.register(meterRegistry);
this.pendingConnectionGauge = meterRegistry
.gaugeBuilder(METRIC_NAME_PENDING_CONNECTIONS, Integer.class, (i) -> poolStats.getPendingThreads())
this.pendingConnectionGauge = Gauge.builder(METRIC_NAME_PENDING_CONNECTIONS, poolStats, PoolStats::getPendingThreads)
.description("Pending threads")
.tags(METRIC_CATEGORY, poolName)
.create();
.register(meterRegistry);
this.maxConnectionGauge = Gauge.builder(METRIC_NAME_MAX_CONNECTIONS, poolStats, PoolStats::getMaxConnections)
.description("Max connections")
.tags(METRIC_CATEGORY, poolName)
.register(meterRegistry);
this.minConnectionGauge = Gauge.builder(METRIC_NAME_MIN_CONNECTIONS, poolStats, PoolStats::getMinConnections)
.description("Min connections")
.tags(METRIC_CATEGORY, poolName)
.register(meterRegistry);
}
/** {@inheritDoc} */
......@@ -98,18 +134,18 @@ public class MicrometerMetricsTracker implements IMetricsTracker
@Override
public void recordConnectionUsageMillis(final long elapsedBorrowedMillis)
{
connectionUsage.record(elapsedBorrowedMillis);
connectionUsage.record(elapsedBorrowedMillis, TimeUnit.MILLISECONDS);
}
@Override
public void recordConnectionTimeout()
{
connectionTimeoutMeter.count();
connectionTimeoutCounter.increment();
}
@Override
public void recordConnectionCreatedMillis(long connectionCreatedMillis)
{
connectionCreation.record(connectionCreatedMillis);
connectionCreation.record(connectionCreatedMillis, TimeUnit.MILLISECONDS);
}
}
......@@ -42,7 +42,11 @@ class HikariCPCollector extends Collector {
createGauge("hikaricp_pending_threads", "Pending threads",
PoolStats::getPendingThreads),
createGauge("hikaricp_connections", "The number of current connections",
PoolStats::getTotalConnections)
PoolStats::getTotalConnections),
createGauge("hikaricp_max_connections", "Max connections",
PoolStats::getMaxConnections),
createGauge("hikaricp_min_connections", "Min connections",
PoolStats::getMinConnections)
);
}
......
......@@ -17,10 +17,9 @@
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
......@@ -30,23 +29,31 @@ class PrometheusMetricsTracker implements IMetricsTracker
.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")
private static final Summary ELAPSED_ACQUIRED_SUMMARY =
registerSummary("hikaricp_connection_acquired_nanos", "Connection acquired time (ns)");
private static final Summary ELAPSED_BORROWED_SUMMARY =
registerSummary("hikaricp_connection_usage_millis", "Connection usage (ms)");
private static final Summary ELAPSED_CREATION_SUMMARY =
registerSummary("hikaricp_connection_creation_millis", "Connection creation (ms)");
private final Counter.Child connectionTimeoutCounterChild;
private static Summary registerSummary(String name, String help) {
return Summary.build()
.name(name)
.labelNames("pool")
.help("Connection creation (ms)")
.help(help)
.quantile(0.5, 0.05)
.quantile(0.95, 0.01)
.quantile(0.99, 0.001)
.maxAgeSeconds(TimeUnit.MINUTES.toSeconds(5))
.ageBuckets(5)
.register();
}
private final Counter.Child connectionTimeoutCounterChild;
private final Summary.Child elapsedAcquiredSummaryChild;
private final Summary.Child elapsedBorrowedSummaryChild;
private final Summary.Child elapsedCreationSummaryChild;
......
......@@ -16,17 +16,22 @@
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 static com.zaxxer.hikari.util.ClockSource.plusMillis;
import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_IN_USE;
import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_NOT_IN_USE;
import static com.zaxxer.hikari.util.UtilityElf.createThreadPoolExecutor;
import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
import static java.util.Collections.unmodifiableCollection;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.health.HealthCheckRegistry;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariPoolMXBean;
import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
import com.zaxxer.hikari.metrics.PoolStats;
import com.zaxxer.hikari.metrics.dropwizard.CodahaleHealthChecker;
import com.zaxxer.hikari.metrics.dropwizard.CodahaleMetricsTrackerFactory;
import com.zaxxer.hikari.metrics.micrometer.MicrometerMetricsTrackerFactory;
import com.zaxxer.hikari.util.ConcurrentBag;
import com.zaxxer.hikari.util.ConcurrentBag.IBagStateListener;
import com.zaxxer.hikari.util.SuspendResumeLock;
import com.zaxxer.hikari.util.UtilityElf.DefaultThreadFactory;
import io.micrometer.core.instrument.MeterRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.SQLException;
......@@ -35,7 +40,6 @@ 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.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
......@@ -45,23 +49,18 @@ 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;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.health.HealthCheckRegistry;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariPoolMXBean;
import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
import com.zaxxer.hikari.metrics.PoolStats;
import com.zaxxer.hikari.metrics.dropwizard.CodahaleHealthChecker;
import com.zaxxer.hikari.metrics.dropwizard.CodahaleMetricsTrackerFactory;
import com.zaxxer.hikari.util.ConcurrentBag;
import com.zaxxer.hikari.util.ConcurrentBag.IBagStateListener;
import com.zaxxer.hikari.util.SuspendResumeLock;
import com.zaxxer.hikari.util.UtilityElf.DefaultThreadFactory;
import static com.zaxxer.hikari.util.ClockSource.currentTime;
import static com.zaxxer.hikari.util.ClockSource.elapsedDisplayString;
import static com.zaxxer.hikari.util.ClockSource.elapsedMillis;
import static com.zaxxer.hikari.util.ClockSource.plusMillis;
import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_IN_USE;
import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_NOT_IN_USE;
import static com.zaxxer.hikari.util.UtilityElf.createThreadPoolExecutor;
import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
import static com.zaxxer.hikari.util.UtilityElf.safeIsAssignableFrom;
import static java.util.Collections.unmodifiableCollection;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
/**
* This is the primary connection pool class that provides the basic
......@@ -73,16 +72,19 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
{
private final Logger LOGGER = LoggerFactory.getLogger(HikariPool.class);
private static final int POOL_NORMAL = 0;
private static final int POOL_SUSPENDED = 1;
private static final int POOL_SHUTDOWN = 2;
public static final int POOL_NORMAL = 0;
public static final int POOL_SUSPENDED = 1;
public static final int POOL_SHUTDOWN = 2;
private volatile int poolState;
public volatile int poolState;
private final long ALIVE_BYPASS_WINDOW_MS = Long.getLong("com.zaxxer.hikari.aliveBypassWindowMs", MILLISECONDS.toMillis(500));
private final long HOUSEKEEPING_PERIOD_MS = Long.getLong("com.zaxxer.hikari.housekeeping.periodMs", SECONDS.toMillis(30));
private final PoolEntryCreator POOL_ENTRY_CREATOR = new PoolEntryCreator(null);
private static final String EVICTED_CONNECTION_MESSAGE = "(connection was evicted)";
private static final String DEAD_CONNECTION_MESSAGE = "(connection is dead)";
private final PoolEntryCreator POOL_ENTRY_CREATOR = new PoolEntryCreator(null /*logging prefix*/);
private final PoolEntryCreator POST_FILL_POOL_ENTRY_CREATOR = new PoolEntryCreator("After adding ");
private final Collection<Runnable> addConnectionQueue;
private final ThreadPoolExecutor addConnectionExecutor;
......@@ -93,7 +95,7 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
private final ProxyLeakTaskFactory leakTaskFactory;
private final SuspendResumeLock suspendResumeLock;
private ScheduledExecutorService houseKeepingExecutorService;
private final ScheduledExecutorService houseKeepingExecutorService;
private ScheduledFuture<?> houseKeeperTask;
/**
......@@ -160,17 +162,15 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
try {
long timeout = hardTimeout;
PoolEntry poolEntry = null;
try {
do {
poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
if (poolEntry == null) {
break; // We timed out... break and throw exception
}
final long now = currentTime();
if (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > ALIVE_BYPASS_WINDOW_MS && !isConnectionAlive(poolEntry.connection))) {
closeConnection(poolEntry, "(connection is evicted or dead)"); // Throw away the dead connection (passed max age or failed alive test)
closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE);
timeout = hardTimeout - elapsedMillis(startTime);
}
else {
......@@ -180,20 +180,15 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
} while (timeout > 0L);
metricsTracker.recordBorrowTimeoutStats(startTime);
throw createTimeoutException(startTime);
}
catch (InterruptedException e) {
if (poolEntry != null) {
poolEntry.recycle(startTime);
}
Thread.currentThread().interrupt();
throw new SQLException(poolName + " - Interrupted during connection acquisition", e);
}
}
finally {
suspendResumeLock.release();
}
throw createTimeoutException(startTime);
}
/**
......@@ -207,7 +202,7 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
try {
poolState = POOL_SHUTDOWN;
if (addConnectionExecutor == null) {
if (addConnectionExecutor == null) { // pool never started
return;
}
......@@ -253,9 +248,9 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
}
/**
* Evict a connection from the pool.
* Evict a Connection from the pool.
*
* @param connection the connection to evict
* @param connection the Connection to evict (actually a {@link ProxyConnection})
*/
public void evictConnection(Connection connection)
{
......@@ -270,12 +265,18 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
}
}
/**
* Set a metrics registry to be used when registering metrics collectors. The HikariDataSource prevents this
* method from being called more than once.
*
* @param metricRegistry the metrics registry instance to use
*/
public void setMetricRegistry(Object metricRegistry)
{
if (metricRegistry instanceof MetricRegistry) {
if (metricRegistry != null && safeIsAssignableFrom(metricRegistry, "com.codahale.metrics.MetricRegistry")) {
setMetricsTrackerFactory(new CodahaleMetricsTrackerFactory((MetricRegistry) metricRegistry));
}
else if (metricRegistry instanceof MeterRegistry) {
else if (metricRegistry != null && safeIsAssignableFrom(metricRegistry, "io.micrometer.core.instrument.MeterRegistry")) {
setMetricsTrackerFactory(new MicrometerMetricsTrackerFactory((MeterRegistry) metricRegistry));
}
else {
......@@ -283,6 +284,11 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
}
}
/**
* Set the MetricsTrackerFactory to be used to create the IMetricsTracker instance used by the pool.
*
* @param metricsTrackerFactory an instance of a class that subclasses MetricsTrackerFactory
*/
public void setMetricsTrackerFactory(MetricsTrackerFactory metricsTrackerFactory)
{
if (metricsTrackerFactory != null) {
......@@ -293,6 +299,12 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
}
}
/**
* Set the health check registry to be used when registering health checks. Currently only Codahale health
* checks are supported.
*
* @param healthCheckRegistry the health check registry instance to use
*/
public void setHealthCheckRegistry(Object healthCheckRegistry)
{
if (healthCheckRegistry != null) {
......@@ -312,8 +324,6 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
if (shouldAdd) {
addConnectionExecutor.submit(POOL_ENTRY_CREATOR);
}
CompletableFuture.completedFuture(Boolean.TRUE);
}
// ***********************************************************************
......@@ -435,12 +445,14 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
return connectionBag.getStateCounts();
}
// ***********************************************************************
// Private methods
// ***********************************************************************
/**
* Creating new poolEntry.
* Creating new poolEntry. If maxLifetime is configured, create a future End-of-life task with 2.5% variance from
* the maxLifetime time to ensure there is no massive die-off of Connections in the pool.
*/
private PoolEntry createPoolEntry()
{
......@@ -464,7 +476,7 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
return poolEntry;
}
catch (Exception e) {
if (poolState == POOL_NORMAL) {
if (poolState == POOL_NORMAL) { // we check POOL_NORMAL to avoid a flood of messages if shutdown() is running concurrently
LOGGER.debug("{} - Cannot acquire connection from data source", poolName, (e instanceof ConnectionSetupException ? e.getCause() : e));
}
return null;
......@@ -485,6 +497,8 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
/**
* Attempt to abort or close active connections.
*
* @param assassinExecutor the ExecutorService to pass to Connection.abort()
*/
private void abortActiveConnections(final ExecutorService assassinExecutor)
{
......@@ -506,6 +520,7 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
* If initializationFailFast is configured, check that we have DB connectivity.
*
* @throws PoolInitializationException if fails to create or validate connection
* @see HikariConfig#setInitializationFailTimeout(long)
*/
private void checkFailFast()
{
......@@ -533,7 +548,7 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
throwPoolInitializationException(getLastConnectionFailure().getCause());
}
quietlySleep(1000L);
quietlySleep(SECONDS.toMillis(1));
} while (elapsedMillis(startTime) < initializationTimeout);
if (initializationTimeout > 0) {
......@@ -541,6 +556,12 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
}
}
/**
* Log the Throwable that caused pool initialization to fail, and then throw a PoolInitializationException with
* that cause attached.
*
* @param t the Throwable that caused the pool to fail to initialize (possibly null)
*/
private void throwPoolInitializationException(Throwable t)
{
LOGGER.error("{} - Exception during pool initialization.", poolName, t);
......@@ -548,6 +569,19 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
throw new PoolInitializationException(t);
}
/**
* "Soft" evict a Connection (/PoolEntry) from the pool. If this method is being called by the user directly
* through {@link com.zaxxer.hikari.HikariDataSource#evictConnection(Connection)} then {@code owner} is {@code true}.
*
* If the caller is the owner, or if the Connection is idle (i.e. can be "reserved" in the {@link ConcurrentBag}),
* then we can close the connection immediately. Otherwise, we leave it "marked" for eviction so that it is evicted
* the next time someone tries to acquire it from the pool.
*
* @param poolEntry the PoolEntry (/Connection) to "soft" evict from the pool
* @param reason the reason that the connection is being evicted
* @param owner true if the caller is the owner of the connection, false otherwise
* @return true if the connection was evicted (closed), false if it was merely marked for eviction
*/
private boolean softEvictConnection(final PoolEntry poolEntry, final String reason, final boolean owner)
{
poolEntry.markEvicted();
......@@ -559,6 +593,13 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
return false;
}
/**
* Create/initialize the Housekeeping service {@link ScheduledExecutorService}. If the user specified an Executor
* to be used in the {@link HikariConfig}, then we use that. If no Executor was specified (typical), then create
* an Executor and configure it.
*
* @return either the user specified {@link ScheduledExecutorService}, or the one we created
*/
private ScheduledExecutorService initializeHouseKeepingExecutorService()
{
if (config.getScheduledExecutor() == null) {
......@@ -573,6 +614,9 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
}
}
/**
* Destroy (/shutdown) the Housekeeping service Executor, if it was the one that we created.
*/
private void destroyHouseKeepingExecutorService()
{
if (config.getScheduledExecutor() == null) {
......@@ -580,6 +624,11 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
}
}
/**
* Create a PoolStats instance that will be used by metrics tracking, with a pollable resolution of 1 second.
*
* @return a PoolStats instance
*/
private PoolStats getPoolStats()
{
return new PoolStats(SECONDS.toMillis(1)) {
......@@ -589,10 +638,24 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
this.idleConnections = HikariPool.this.getIdleConnections();
this.totalConnections = HikariPool.this.getTotalConnections();
this.activeConnections = HikariPool.this.getActiveConnections();
this.maxConnections = config.getMaximumPoolSize();
this.minConnections = config.getMinimumIdle();
}
};
}
/**
* Create a timeout exception (specifically, {@link SQLTransientConnectionException}) to be thrown, because a
* timeout occurred when trying to acquire a Connection from the pool. If there was an underlying cause for the
* timeout, e.g. a SQLException thrown by the driver while trying to create a new Connection, then use the
* SQL State from that exception as our own and additionally set that exception as the "next" SQLException inside
* of our exception.
*
* As a side-effect, log the timeout failure at DEBUG, and record the timeout failure in the metrics tracker.
*
* @param startTime the start time (timestamp) of the acquisition attempt
* @return a SQLException to be thrown from {@link #getConnection()}
*/
private SQLException createTimeoutException(long startTime)
{
logPoolState("Timeout failure ");
......@@ -611,6 +674,7 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
return connectionException;
}
// ***********************************************************************
// Non-anonymous Inner-classes
// ***********************************************************************
......@@ -650,9 +714,13 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
return Boolean.FALSE;
}
/**
* We only create connections if we need another idle connection or have threads still waiting
* for a new connection. Otherwise we bail out of the request to create.
*
* @return true if we should create a connection, false if the need has disappeared
*/
private boolean shouldCreateAnotherConnection() {
// only create connections if we need another idle connection or have threads still waiting
// for a new connection, otherwise bail
return getTotalConnections() < config.getMaximumPoolSize() &&
(connectionBag.getWaitingThreadCount() > 0 || getIdleConnections() < config.getMinimumIdle());
}
......@@ -683,7 +751,6 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
poolName, elapsedDisplayString(previous, now));
previous = now;
softEvictConnections();
fillPool();
return;
}
else if (now > plusMillis(previous, (3 * HOUSEKEEPING_PERIOD_MS) / 2)) {
......@@ -699,13 +766,11 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
afterPrefix = "After cleanup ";
final List<PoolEntry> notInUse = connectionBag.values(STATE_NOT_IN_USE);
int removed = 0;
int toRemove = notInUse.size() - config.getMinimumIdle();
for (PoolEntry entry : notInUse) {
if (elapsedMillis(entry.lastAccessed, now) > idleTimeout && connectionBag.reserve(entry)) {
if (toRemove > 0 && elapsedMillis(entry.lastAccessed, now) > idleTimeout && connectionBag.reserve(entry)) {
closeConnection(entry, "(connection has passed idleTimeout)");
if (++removed > config.getMinimumIdle()) {
break;
}
toRemove--;
}
}
}
......
......@@ -16,10 +16,21 @@
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;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.metrics.IMetricsTracker;
import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException;
import com.zaxxer.hikari.util.DriverDataSource;
import com.zaxxer.hikari.util.PropertyElf;
import com.zaxxer.hikari.util.UtilityElf;
import com.zaxxer.hikari.util.UtilityElf.DefaultThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.lang.management.ManagementFactory;
import java.sql.Connection;
import java.sql.SQLException;
......@@ -30,45 +41,23 @@ 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;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.metrics.IMetricsTracker;
import com.zaxxer.hikari.util.DriverDataSource;
import com.zaxxer.hikari.util.PropertyElf;
import com.zaxxer.hikari.util.UtilityElf;
import com.zaxxer.hikari.util.UtilityElf.DefaultThreadFactory;
import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_AUTOCOMMIT;
import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_CATALOG;
import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_ISOLATION;
import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_NETTIMEOUT;
import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_READONLY;
import static com.zaxxer.hikari.util.ClockSource.currentTime;
import static com.zaxxer.hikari.util.ClockSource.elapsedMillis;
import static com.zaxxer.hikari.util.ClockSource.elapsedNanos;
import static com.zaxxer.hikari.pool.ProxyConnection.*;
import static com.zaxxer.hikari.util.ClockSource.*;
import static com.zaxxer.hikari.util.UtilityElf.createInstance;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
abstract class PoolBase
{
private final Logger LOGGER = LoggerFactory.getLogger(PoolBase.class);
protected final HikariConfig config;
public final HikariConfig config;
public IMetricsTrackerDelegate metricsTracker;
protected final String poolName;
long connectionTimeout;
long validationTimeout;
IMetricsTrackerDelegate metricsTracker;
private static final String[] RESET_STATES = {"readOnly", "autoCommit", "isolation", "catalog", "netTimeout", "schema"};
private static final int UNINITIALIZED = -1;
......@@ -155,15 +144,15 @@ abstract class PoolBase
try {
setNetworkTimeout(connection, validationTimeout);
final long validationSeconds = (int) Math.max(1000L, validationTimeout) / 1000;
final int validationSeconds = (int) Math.max(1000L, validationTimeout) / 1000;
if (isUseJdbc4Validation) {
return connection.isValid((int) validationSeconds);
return connection.isValid(validationSeconds);
}
try (Statement statement = connection.createStatement()) {
if (isNetworkTimeoutSupported != TRUE) {
setQueryTimeout(statement, (int) validationSeconds);
setQueryTimeout(statement, validationSeconds);
}
statement.execute(config.getConnectionTestQuery());
......
......@@ -78,6 +78,7 @@ public abstract class ProxyConnection implements Connection
LOGGER = LoggerFactory.getLogger(ProxyConnection.class);
ERROR_STATES = new HashSet<>();
ERROR_STATES.add("0A000"); // FEATURE UNSUPPORTED
ERROR_STATES.add("57P01"); // ADMIN SHUTDOWN
ERROR_STATES.add("57P02"); // CRASH SHUTDOWN
ERROR_STATES.add("57P03"); // CANNOT CONNECT NOW
......