summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorApollon Oikonomopoulos <apoikos@debian.org>2017-09-13 00:26:38 +0300
committerApollon Oikonomopoulos <apoikos@debian.org>2017-09-13 00:26:38 +0300
commitc123261d8f14b0b5ca2c18e3648b17264f65438f (patch)
treefa1a49f0258134ff1bd55f44fae128c1dd86f55f
parent3492d46519ea3fe5fa823115df6f8fa9c4c98879 (diff)
parent808d040ea9d760bf468621984a3f2de865d35e7c (diff)
Updated version 2.7.1 from 'upstream/2.7.1'
with Debian dir 15a46d9a58caaa2a40674db085e213646be22b07
-rw-r--r--.github/ISSUE_TEMPLATE.md8
-rw-r--r--.travis.yml33
-rw-r--r--CHANGES88
-rw-r--r--README.md143
-rw-r--r--documents/Wall-of-Fame.md20
-rw-r--r--documents/Welcome-To-The-Jungle.md30
-rw-r--r--logo.pngbin0 -> 8697 bytes
-rw-r--r--pom.xml102
-rw-r--r--src/main/java/com/zaxxer/hikari/HikariConfig.java161
-rw-r--r--src/main/java/com/zaxxer/hikari/HikariDataSource.java30
-rw-r--r--src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTracker.java115
-rw-r--r--src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerFactory.java22
-rw-r--r--src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java48
-rw-r--r--src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java94
-rw-r--r--src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java25
-rwxr-xr-xsrc/main/java/com/zaxxer/hikari/pool/HikariPool.java115
-rwxr-xr-xsrc/main/java/com/zaxxer/hikari/pool/PoolBase.java84
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/PoolEntry.java31
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java88
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyFactory.java15
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyLeakTask.java28
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyLeakTaskFactory.java54
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyPreparedStatement.java4
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyResultSet.java11
-rw-r--r--src/main/java/com/zaxxer/hikari/pool/ProxyStatement.java22
-rwxr-xr-xsrc/main/java/com/zaxxer/hikari/util/ConcurrentBag.java32
-rw-r--r--src/main/java/com/zaxxer/hikari/util/DriverDataSource.java25
-rw-r--r--src/main/java/com/zaxxer/hikari/util/QueuedSequenceSynchronizer.java137
-rw-r--r--src/main/java/com/zaxxer/hikari/util/Sequence.java71
-rwxr-xr-xsrc/test/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerTest.java39
-rw-r--r--src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java11
-rw-r--r--src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java44
-rw-r--r--src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java2
-rw-r--r--src/test/java/com/zaxxer/hikari/mocks/StubStatement.java11
-rw-r--r--src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java118
-rwxr-xr-xsrc/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java4
-rwxr-xr-xsrc/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java2
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java2
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/MetricsTrackerTest.java86
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/MiscTest.java7
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/SaturatedPoolTest830.java156
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java5
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestElf.java23
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestJNDI.java27
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestMBean.java93
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestMetrics.java46
-rw-r--r--src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java112
47 files changed, 1639 insertions, 785 deletions
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..03442d0
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,8 @@
+#### Environment
+```
+HikariCP version: x.x.x
+JDK version : 1.8.0_111
+Database : PostgreSQL|MySQL|...
+Driver version : x.x.x
+```
+-----------------------------------------------------------------------------------------
diff --git a/.travis.yml b/.travis.yml
index ddd2ed0..9cb01c7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,13 +1,36 @@
-language: java
sudo: false
+language: java
+
+dist:
+ - trusty
+
+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)
+ - export JDK9_HOME=/usr/lib/jvm/java-9-oracle
+ - test -d "${JDK9_HOME}" || export JDK9_HOME=$(jdk_switcher home oraclejdk8)
+
+before_cache:
+ # No sense in caching current build artifacts
+ - rm -rf $HOME/.m2/repository/com/zaxxer
+
cache:
directories:
- $HOME/.m2/repository
-jdk:
- - oraclejdk8
+
+matrix:
+ fast_finish: true
+ include:
+ - jdk: oraclejdk8
+ - jdk: oraclejdk9
+ env:
+ - JDK=9
install: /bin/true
script:
- - mvn install -Dskip.unit.tests=true -Dmaven.javadoc.skip=true -V -B
- - mvn test coveralls:report -V -P coverage
+ - mvn package -Dskip.unit.tests=true -Dmaven.javadoc.skip=true -V -B
+ - mvn test $COVERALLS -V -P coverage
diff --git a/CHANGES b/CHANGES
index 6da7220..081bec4 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,13 +1,71 @@
HikariCP Changes
+Changes in 2.7.1
+
+ * issue 968 Wrong label order in MicrometerMetricsTracker for the connection usage
+ metric.
+
+ * issue 967 incorrect bitwise operator value in ConcurrentBag.requite method
+ intended to cause parkNanos() to be called every 256 iterations. Thanks to @ztkmkoo
+ for finding this.
+
+Changes in 2.7.0
+
+ * added support for micrometer metrics (currently Alpha-level support).
+
+ * issue 905 mask JDBC password in URLs
+
+ * issue 940 fix Prometheus metric collector for multiple data config
+
+ * issue 941 add support for setting a default schema
+
+ * issue 955 fix possible race condition when Statements are closed on different
+ threads from which they were created.
+
+Changes in 2.6.3
+
+ * issue 878 load driver class from ThreadContext classloader if it is not found
+ via the regular classloader.
+
+Changes in 2.6.2
+
+ * issue 890 add support for Prometheus metrics and multiple HikariCP pools.
+
+ * issue 880 fix race condition caused by sorting collection while the condition of
+ sort can change.
+
+ * issue 876 add support for using a Prometheus CollectorRegistry other than the
+ default one.
+
+ * issue 867 support network timeout even for Connection.isValid().
+
+ * issue 866 mark commit state dirty when Connection.getMetaData() is called.
+
+Changes in 2.6.1
+
+ * issue 821 if a disconnection class exception is thrown during initial connection
+ setup, do not set the flag that indicates that checkDriverSupport() is complete.
+
+ * issue 835 fix increased CPU consumption under heavy load caused by excessive
+ spinning in the ConcurrentBag.requite() method.
+
+ * issue 817 updated behavior of new initializationFailTimeout, please see the
+ official documentation for details.
+
+ * issue 742 add direct MXBean accessor methods to HikariDataSource for users who do
+ not want run run JMX.
+
Changes in 2.6.0
+ * Redesign of the contention code path resulting in doubling contended throughput; now
+ contended pool access retains 98% of the uncontended throughput.
+
* issue 793 add new HikariConfig method, setScheduledExecutor(ScheduledExecutorService),
and deprecate method setScheduledExecutorService(ScheduledThreadPoolExecutor). It is
unfortunate that the deprecated method has the more accurate name, but its signature
cannot be changed without breaking binary compatibility.
- * issue 770 adding a new property initializationFailTimeout and deprecate configuration
+ * issue 770 add a new property initializationFailTimeout, and deprecate configuration
property initializationFailFast.
* issue 774 significantly improve spike load handling.
@@ -40,6 +98,34 @@ Changes in 2.5.0
* HikariCP 2.5.0 and HikariCP-java7 2.4.8 have identical functionality.
+Changes in 2.4.12
+
+ * issue 878 search for driverClass in both HikariCP class classloader and Thread Context
+ ClassLoader
+
+Changes in 2.4.11
+
+ * issue 793 add new HikariConfig method, setScheduledExecutor(ScheduledExecutorService),
+ and deprecate method setScheduledExecutorService(ScheduledThreadPoolExecutor). It is
+ unfortunate that the deprecated method has the more accurate name, but its signature
+ cannot be changed without breaking binary compatibility.
+
+ * issue 600 ignore Java 8 default methods when generating proxy classes for Java 7.
+
+Changes in 2.4.10
+
+ * Redesign of the contention code path resulting in doubling contended throughput; now
+ contended pool access retains 98% of the uncontended throughput.
+
+ * issue 770 add a new property initializationFailTimeout, and deprecate configuration
+ property initializationFailFast.
+
+ * issue 774 significantly improve spike load handling.
+
+ * issue 741 cancel HouseKeeper task on pool shutdown. If the ScheduledExecutor being used
+ did not belong to HikariCP, this task would remain scheduled after shutdown, causing a
+ memory leak.
+
Changes in 2.4.9
* issue 719 only reset lastConnectionFailure after a successful dataSource.getConnection()
diff --git a/README.md b/README.md
index c0068ac..36ccf1d 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,13 @@
-<h1>![](https://github.com/brettwooldridge/HikariCP/wiki/Hikari.png) HikariCP<sup><sup>&nbsp;It's Faster.</sup></sup><sub><sub><sup>Hi·ka·ri [hi·ka·'lē] &#40;*Origin: Japanese*): light; ray.</sup></sub></sub></h1><br>
+<h1><img src="https://github.com/brettwooldridge/HikariCP/wiki/Hikari.png"> HikariCP<sup><sup>&nbsp;It's Faster.</sup></sup><sub><sub><sup>Hi·ka·ri [hi·ka·'lē] &#40;<i>Origin: Japanese</i>): light; ray.</sup></sub></sub></h1><br>
+
[![][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]
-Fast, simple, reliable. HikariCP is a "zero-overhead" production ready JDBC connection pool. At roughly 90Kb, the library is very light. Read about [how we do it here](https://github.com/brettwooldridge/HikariCP/wiki/Down-the-Rabbit-Hole).
+Fast, simple, reliable. HikariCP is a "zero-overhead" production ready JDBC connection pool. At roughly 130Kb, the library is very light. Read about [how we do it here](https://github.com/brettwooldridge/HikariCP/wiki/Down-the-Rabbit-Hole).
&nbsp;&nbsp;&nbsp;<sup>**"Simplicity is prerequisite for reliability."**<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- *Edsger Dijkstra*</sup>
@@ -17,7 +19,15 @@ _Java 8 maven artifact:_
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
- <version>2.5.1</version>
+ <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>
</dependency>
```
_Java 7 maven artifact (*maintenance mode*):_
@@ -25,7 +35,7 @@ _Java 7 maven artifact (*maintenance mode*):_
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP-java7</artifactId>
- <version>2.4.9</version>
+ <version>2.4.12</version>
</dependency>
```
_Java 6 maven artifact (*maintenance mode*):_
@@ -39,45 +49,58 @@ _Java 6 maven artifact (*maintenance mode*):_
Or [download from here](http://search.maven.org/#search%7Cga%7C1%7Ccom.zaxxer.hikaricp).
----------------------------------------------------
-[![](https://github.com/brettwooldridge/HikariCP/wiki/LudicrousBlog.png)](http://brettwooldridge.github.io/HikariCP/ludicrous.html)
-##### JMH Benchmarks
+##### JMH Benchmarks :checkered_flag:
-Microbenchmarks were created to isolate and measure the overhead of pools using the [JMH microbenchmark framework](http://openjdk.java.net/projects/code-tools/jmh/) developed by the Oracle JVM performance team. You can checkout the [HikariCP benchmark project for details](https://github.com/brettwooldridge/HikariCP-benchmark) and review/run the benchmarks yourself.
+Microbenchmarks were created to isolate and measure the overhead of pools using the [JMH microbenchmark framework](http://openjdk.java.net/projects/code-tools/jmh/). You can checkout the [HikariCP benchmark project for details](https://github.com/brettwooldridge/HikariCP-benchmark) and review/run the benchmarks yourself.
-![](https://github.com/brettwooldridge/HikariCP/wiki/HikariCP-bench-2.4.0.png)
+![](https://github.com/brettwooldridge/HikariCP/wiki/HikariCP-bench-2.6.0.png)
* One *Connection Cycle* is defined as single ``DataSource.getConnection()``/``Connection.close()``.
- * In *Unconstrained* benchmark, connections > threads.
- * In *Constrained* benchmark, threads > connections (2:1).
* One *Statement Cycle* is defined as single ``Connection.prepareStatement()``, ``Statement.execute()``, ``Statement.close()``.
<sup>
-<sup>1</sup> Versions: HikariCP 2.4.0, commons-dbcp2 2.1, Tomcat 8.0.23, Vibur 3.0, c3p0 0.9.5.1, Java 8u45 <br/>
-<sup>2</sup> Java options: -server -XX:+AggressiveOpts -XX:+UseFastAccessorMethods -Xmx512m <br/>
+<sup>1</sup> Versions: HikariCP 2.6.0, commons-dbcp2 2.1.1, Tomcat 8.0.24, Vibur 16.1, c3p0 0.9.5.2, Java 8u111 <br/>
+<sup>2</sup> Intel Core i7-3770 CPU @ 3.40GHz <br/>
+<sup>3</sup> Uncontended benchmark: 32 threads/32 connections, Contended benchmark: 32 threads, 16 connections <br/>
+<sup>4</sup> Apache Tomcat fails to complete the Statement benchmark when the Tomcat <i>StatementFinalizer</i> is used <a href="https://raw.githubusercontent.com/wiki/brettwooldridge/HikariCP/markdown/Tomcat-Statement-Failure.md">due to excessive garbage collection times</a><br/>
+<sup>5</sup> Apache DBCP fails to complete the Statement benchmark <a href="https://raw.githubusercontent.com/wiki/brettwooldridge/HikariCP/markdown/Dbcp2-Statement-Failure.md">due to excessive garbage collection times</a>
</sup>
----------------------------------------------------
-##### User Testimonials
+#### Analyses :microscope:
-[![](https://github.com/brettwooldridge/HikariCP/wiki/tweet3.png)](https://twitter.com/jkuipers)<br/>
-[![](https://github.com/brettwooldridge/HikariCP/wiki/tweet1.png)](https://twitter.com/steve_objectify)<br/>
-[![](https://github.com/brettwooldridge/HikariCP/wiki/tweet2.png)](https://twitter.com/brettemeyer)<br/>
-[![](https://github.com/brettwooldridge/HikariCP/wiki/tweet4.png)](https://twitter.com/dgomesbr)
-
-In the words of the guys over at [Edulify](https://edulify.com), *"HikariCP is supposed to be the fastest connection pool in Java land. But we did not start to use it because of speed, but because of its reliability. Here is a cool graph that shows connections opened to PostgreSQL. As you can see, the pool is way more stable. Also it is keeping its size at the minimum since we deploy it."*
+#### Spike Demand Pool Comparison
+<a href="https://github.com/brettwooldridge/HikariCP/blob/dev/documents/Welcome-To-The-Jungle.md"><img width="400" align="right" src="https://github.com/brettwooldridge/HikariCP/wiki/Spike-Hikari.png"></a>
+Anaylsis of HikariCP v2.6, in comparison to other pools, in relation to a unique "spike demand" load.
-![](https://github.com/brettwooldridge/HikariCP/wiki/HikariVsBone.png)
-
-----------------------------------------------------
+The customer's environment imposed a high cost of new connection acquisition, and a requirement for a dynamically-sized pool, but yet a need for responsiveness to request spikes. Read about the spike demand handling [here](https://github.com/brettwooldridge/HikariCP/blob/dev/documents/Welcome-To-The-Jungle.md).
+<br/>
+<br/>
+#### You're [probably] doing it wrong.
+<a href=""><img width="200" align="right" src="https://github.com/brettwooldridge/HikariCP/wiki/Postgres_Chart.png"></a>
+AKA *"What you probably didn't know about connection pool sizing"*. Watch a video from the Oracle Real-world Performance group, and learn about why connection pools do not need to be sized as large as they often are. In fact, oversized connection pools have a clear and demonstrable *negative* impact on performance; a 50x difference in the case of the Oracle demonstration. [Read on to find out](https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing).
+<br/>
#### WIX Engineering Analysis
+<a href="http://engineering.wix.com/2015/04/28/how-does-hikaricp-compare-to-other-connection-pools/"><img width="180" align="left" src="https://github.com/brettwooldridge/HikariCP/wiki/Wix-Engineering.png"></a>
We'd like to thank the guys over at WIX for the unsolicited and deep write-up about HikariCP on their [engineering blog](http://engineering.wix.com/2015/04/28/how-does-hikaricp-compare-to-other-connection-pools/). Take a look if you have time.
-
+<br/>
+<br/>
+<br/>
#### Failure: Pools behaving badly
Read our interesting ["Database down" pool challenge](https://github.com/brettwooldridge/HikariCP/wiki/Bad-Behavior:-Handling-Database-Down).
-#### You're [probably] doing it wrong.
-AKA ["What you probably didn't know about connection pool sizing"](https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing). Read on to find out.
+----------------------------------------------------
+#### "Imitation Is The Sincerest Form Of Plagiarism" - <sub><sup>anonymous</sup></sub>
+Open source software like HikariCP, like any product, competes in the free market. We get it. We understand that product advancements, once public, are often co-opted. And we understand that ideas can arise from the zeitgeist; simultaneously and independently. But the timeline of innovation, particularly in open source projects, is also clear and we want our users to understand the direction of flow of innovation in our space. It could be demoralizing to see the result of hundreds of hours of thought and research co-opted so easily, and perhaps that is inherent in a free marketplace, but we are not demoralized. *We are motivated; to widen the gap.*
+
+----------------------------------------------------
+##### User Testimonials
+
+[![](https://github.com/brettwooldridge/HikariCP/wiki/tweet3.png)](https://twitter.com/jkuipers)<br/>
+[![](https://github.com/brettwooldridge/HikariCP/wiki/tweet1.png)](https://twitter.com/steve_objectify)<br/>
+[![](https://github.com/brettwooldridge/HikariCP/wiki/tweet2.png)](https://twitter.com/brettemeyer)<br/>
+[![](https://github.com/brettwooldridge/HikariCP/wiki/tweet4.png)](https://twitter.com/dgomesbr/status/527521925401419776)
------------------------------
#### Configuration (knobs, baby!)
@@ -85,7 +108,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 heavily on accurate high-resolution 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. *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/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.
##### Essentials
@@ -148,22 +171,21 @@ 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 mimimum
+this timeout. 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. When a connection
-reaches this timeout it will be retired from the pool, subject to a maximum variation of +30
-seconds. 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-level connection timeout.** A value of 0 indicates no maximum lifetime
-(infinite lifetime), subject of course to the ``idleTimeout`` setting.
+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
+connection time limit.** A value of 0 indicates no maximum lifetime (infinite lifetime), subject of
+course to the ``idleTimeout`` setting.
*Default: 1800000 (30 minutes)*
&#128288;``connectionTestQuery``<br/>
**If your driver supports JDBC4 we strongly recommend not setting this property.** This is for
-"legacy" databases that do not support the JDBC4 ``Connection.isValid() API``. This is the query that
+"legacy" drivers that do not support the JDBC4 ``Connection.isValid() API``. This is the query that
will be executed just before a connection is given to you from the pool to validate that the
connection to the database is still alive. *Again, try running the pool without this property,
HikariCP will log an error if your driver is not JDBC4 compliant to let you know.*
@@ -171,10 +193,10 @@ HikariCP will log an error if your driver is not JDBC4 compliant to let you know
&#128290;``minimumIdle``<br/>
This property controls the minimum number of *idle connections* that HikariCP tries to maintain
-in the pool. If the idle connections dip below this value, HikariCP will make a best effort to
-add additional connections quickly and efficiently. However, for maximum performance and
-responsiveness to spike demands, we recommend *not* setting this value and instead allowing
-HikariCP to act as a *fixed size* connection pool.
+in the pool. If the idle connections dip below this value and total connections in the pool are less than ``maximumPoolSize``,
+HikariCP will make a best effort to add additional connections quickly and efficiently.
+However, for maximum performance and responsiveness to spike demands,
+we recommend *not* setting this value and instead allowing HikariCP to act as a *fixed size* connection pool.
*Default: same as maximumPoolSize*
&#128290;``maximumPoolSize``<br/>
@@ -183,13 +205,13 @@ idle and in-use connections. Basically this value will determine the maximum nu
actual connections to the database backend. A reasonable value for this is best determined
by your execution environment. 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.
+before timing out. Please read [about pool sizing](https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing).
*Default: 10*
&#128200;``metricRegistry``<br/>
This property is only available via programmatic configuration or IoC container. This property
allows you to specify an instance of a *Codahale/Dropwizard* ``MetricRegistry`` to be used by the
-pool to record various metrics. See the [Metrics](https://github.com/brettwooldridge/HikariCP/wiki/Codahale-Metrics)
+pool to record various metrics. See the [Metrics](https://github.com/brettwooldridge/HikariCP/wiki/Dropwizard-Metrics)
wiki page for details.
*Default: none*
@@ -207,11 +229,19 @@ in logging and JMX management consoles to identify pools and pool configurations
##### Infrequently used
-&#9989;``initializationFailFast``<br/>
+&#8986;``initializationFailTimeout``<br/>
This property controls whether the pool will "fail fast" if the pool cannot be seeded with
-initial connections successfully. If you want your application to start *even when* the
-database is down/unavailable, set this property to ``false``.
-*Default: true*
+an initial connection successfully. Any positive number is taken to be the number of
+milliseconds to attempt to acquire an initial connection; the application thread will be
+blocked during this period. If a connection cannot be acquired before this timeout occurs,
+an exception will be thrown. This timeout is applied *after* the ``connectionTimeout``
+period. If the value is zero (0), HikariCP will attempt to obtain and validate a connection.
+If a connection is obtained, but fails validation, an exception will be thrown and the pool
+not started. However, if a connection cannot be obtained, the pool will start, but later
+efforts to obtain a connection may fail. A value less than zero will bypass any initial
+connection attempt, and the pool will start immediately while trying to obtain connections
+in the background. Consequently, later efforts to obtain a connection may fail.
+*Default: 1*
&#10062;``isolateInternalQueries``<br/>
This property determines whether HikariCP isolates internal pool queries, such as the
@@ -288,6 +318,15 @@ for creating all threads used by the pool. It is needed in some restricted execu
where threads can only be created through a ``ThreadFactory`` provided by the application container.
*Default: none*
+&#10145;``scheduledExecutor``<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.ScheduledExecutorService`` that will
+be used for various internally scheduled tasks. If supplying HikariCP with a ``ScheduledThreadPoolExecutor``
+instance, it is recommended that ``setRemoveOnCancelPolicy(true)`` is used.
+*Default: none*
+
+----------------------------------------------------
+
#### Missing Knobs
HikariCP has plenty of "knobs" to turn as you can see above, but comparatively less than some other pools.
@@ -325,7 +364,7 @@ good options.
### Initialization
-You can use the ``HikariConfig`` class like so:
+You can use the ``HikariConfig`` class like so<sup>1</sup>:
```java
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/simpsons");
@@ -337,6 +376,8 @@ config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
HikariDataSource ds = new HikariDataSource(config);
```
+&nbsp;<sup><sup>1</sup> MySQL-specific example, DO NOT COPY VERBATIM.</sup>
+
or directly instantiate a ``HikariDataSource`` like so:
```java
HikariDataSource ds = new HikariDataSource();
@@ -377,13 +418,16 @@ There is also a System property available, ``hikaricp.configurationFile``, that
location of a properties file. If you intend to use this option, construct a ``HikariConfig`` or ``HikariDataSource``
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)
+
### Popular DataSource Class Names
We recommended using ``dataSourceClassName`` instead of ``jdbcUrl``, but either is acceptable. We'll say that again, *either is acceptable*.
&#9888;&nbsp;*Note: Spring Boot auto-configuration users, you need to use ``jdbcUrl``-based configuration.*
-&#9888;&nbsp;MysqlDataSource is known to be broken, use ``jdbcUrl`` configuration instead.
+&#9888;&nbsp;The MySQL DataSource is known to be broken with respect to network timeout support. Use ``jdbcUrl`` configuration instead.
Here is a list of JDBC *DataSource* classes for popular databases:
@@ -396,7 +440,7 @@ Here is a list of JDBC *DataSource* classes for popular databases:
| 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~~ | Connector/J | ~~com.mysql.jdbc.jdbc2.optional.MysqlDataSource~~ |
| MySQL/MariaDB | MariaDB | org.mariadb.jdbc.MySQLDataSource |
| Oracle | Oracle | oracle.jdbc.pool.OracleDataSource |
| OrientDB | OrientDB | com.orientechnologies.orient.jdbc.OrientDataSource |
@@ -460,7 +504,10 @@ Please perform changes and submit pull requests from the ``dev`` branch instead
[Dependency Status img]:https://www.versioneye.com/user/projects/551ce51c3661f1bee50004e0/badge.svg?style=flat
[license]:LICENSE
-[license img]:https://img.shields.io/badge/License-Apache%202-blue.svg
+[license img]:https://img.shields.io/badge/license-Apache%202-blue.svg
[Maven Central]:https://maven-badges.herokuapp.com/maven-central/com.zaxxer/HikariCP
[Maven Central img]:https://maven-badges.herokuapp.com/maven-central/com.zaxxer/HikariCP/badge.svg
+
+[Javadocs]:http://javadoc.io/doc/com.zaxxer/HikariCP
+[Javadocs img]:http://javadoc.io/badge/com.zaxxer/HikariCP.svg
diff --git a/documents/Wall-of-Fame.md b/documents/Wall-of-Fame.md
new file mode 100644
index 0000000..4deb6ad
--- /dev/null
+++ b/documents/Wall-of-Fame.md
@@ -0,0 +1,20 @@
+<img width="240" height="240" valign="middle" src="https://pbs.twimg.com/profile_images/671602360075100161/B2WA5XaX.png">![][spacer]<img width="250" height="88" valign="middle" src="https://www.skytap.com/wp-content/uploads/2016/05/Puppets_company_logo.png">![][spacer]<img width="250" height="88" valign="middle" src="https://web.liferay.com/osb-community-theme/images/custom/heading.png">
+
+<img width="240" valign="middle" src="https://www.playframework.com/assets/images/logos/play_full_color.png">![][spacer]<img width="240" valign="middle" src="https://www.atlassian.com/dam/jcr:416a5a7b-55dc-435d-a3e3-22aa2650ba5d/AtlassianLogo.svg">![][spacer]<img width="240" valign="middle" src="http://www.deloittedigital.com/de/assets/img/site-icons/og-site-image.png">
+
+<img width="240" valign="middle" src="https://logo-png.com/logopng/splunk-logo.png">![][spacer]<img width="240" valign="middle" src="https://www.surpasshosting.com/images/comodo_logo.png">![][spacer]<img width="240" valign="middle" src="https://www.ca.com/content/dam/ca/us/images/company/ca-logo-b-w.jpg">
+
+<img width="260" valign="middle" src="http://fiware-cosmos.readthedocs.io/en/r4_fiware/user_and_programmer_manual/streaming/images/storm_logo.png">![][spacer]<img width="240" valign="middle" src="https://image.slidesharecdn.com/apachehive-151229131013/95/apache-hive-1-638.jpg?cb=1451394627">![][spacer]<img width="240" valign="middle" src="http://design.jboss.org/hibernate/logo/final/hibernate_logo_whitebkg.svg">
+
+<img width="240" valign="middle" src="https://spring.io/img/spring-by-pivotal.png">![][spacer]<img width="240" valign="middle" src="https://jaxenter.com/wp-content/uploads/2013/02/slick.jpg">![][spacer]<img width="240" valign="middle" src="https://www.opennms.com/wp-content/uploads/2015/02/openNMSlogo-transparent-noninterlaced-150ppi.png">
+
+<img width="240" valign="middle" src="https://aries.apache.org/images/Arieslogo_Horizontal.gif">
+
+
+<img height="60" src="https://github.com/brettwooldridge/HikariCP/wiki/space60x1.gif"/>
+
+-------------------------------------------------------------------------------------
+ * Images on this page are Trademarked and Copyrighted by their respective rights holders.
+ * Images on this page are used without express permission, and do not constitute an endorsement of HikariCP. If you are a trademark or copyright holder and wish an image to be removed from this page, please open a request in the issue tracker.
+
+[spacer]: https://github.com/brettwooldridge/HikariCP/wiki/space60x1.gif
diff --git a/documents/Welcome-To-The-Jungle.md b/documents/Welcome-To-The-Jungle.md
index 82075c3..36726d0 100644
--- a/documents/Welcome-To-The-Jungle.md
+++ b/documents/Welcome-To-The-Jungle.md
@@ -40,7 +40,7 @@ The code was run as follows:
```
bash$ ./spiketest.sh 150 <pool> 50
```
-Where ``150`` is the connection establishment time, ``<pool>`` is one of *hikari*, *dbcp2*, *vibur*, or *c3p0*, and ``50`` is the number of threads/requests. Note that *c3p0* was dropped from the analysis here, as its run time was ~120x that of HikariCP.
+Where ``150`` is the connection establishment time, ``<pool>`` is one of [*hikari*, *dbcp2*, *vibur*, *tomcat*, *c3p0*], and ``50`` is the number of threads/requests. Note that *c3p0* was dropped from the analysis here, as its run time was ~120x that of HikariCP.
#### HikariCP (v2.6.0) <sub><sup><a href="https://github.com/brettwooldridge/HikariCP/wiki/Spike-Hikari-data.txt">raw data</a></sup></sub>
@@ -52,12 +52,17 @@ Where ``150`` is the connection establishment time, ``<pool>`` is one of *hikari
--------------------
[![](https://github.com/brettwooldridge/HikariCP/wiki/Spike-DBCP2.png)](https://github.com/brettwooldridge/HikariCP/wiki/Spike-DBCP2.png)
+#### Apache Tomcat (v8.0.24) <sub><sup><a href="https://github.com/brettwooldridge/HikariCP/wiki/Spike-Tomcat-data.txt">raw data</a></sup></sub>
+
+--------------------
+[![](https://github.com/brettwooldridge/HikariCP/wiki/Spike-Tomcat.png)](https://github.com/brettwooldridge/HikariCP/wiki/Spike-Tomcat.png)
+
#### Vibur DBCP (v16.1) <sub><sup><a href="https://github.com/brettwooldridge/HikariCP/wiki/Spike-Vibur-data.txt">raw data</a></sup></sub>
--------------------
[![](https://github.com/brettwooldridge/HikariCP/wiki/Spike-Vibur.png)](https://github.com/brettwooldridge/HikariCP/wiki/Spike-Vibur.png)
-<sup>* Note that the times provided in the raw data is the number of microseconds (μs) since the start of the test.</sup>
+<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
@@ -66,27 +71,30 @@ In case you missed the *time-scale* in the graphs above, here is a properly scal
[![](https://github.com/brettwooldridge/HikariCP/wiki/Spike-Compare.png)](https://github.com/brettwooldridge/HikariCP/wiki/Spike-Compare.png)
### Commentary
-We'll start by saying that we are not going to comment on the implementation specifics of Apache or Vibur, but you may be able to draw inferences by our comments regarding HikariCP.
+We'll start by saying that we are not going to comment on the implementation specifics of the other pools, but you may be able to draw inferences by our comments regarding HikariCP.
-Looking at the HikariCP graph, we couldn't have wished for a better profile; it's about as close to perfect efficiency as we could expect. It is interesting, though not surprising, that the Apache and Vibur profiles are so similar to each other; even though arrived at via completely different implementations.
+Looking at the HikariCP graph, we couldn't have wished for a better profile; it's about as close to perfect efficiency as we could expect. It is interesting, though not surprising, that the other pool profiles are so similar to each other. Even though arrived at via different implementations, they are the result of a *conventional* or *obvious* approach to pool design.
HikariCP's profile in this case, and the reason for the difference observed between other pools, is the result of our Prime Directive:
💡 **User threads should only ever block on the** ***pool itself***.<sup>1</sup><br>
<img width="32px" src="https://github.com/brettwooldridge/HikariCP/wiki/space60x1.gif"><sub><sup>1</sup>&nbsp;to the greatest extent possible.</sub>
-What does this mean? Consider this hypothetical scenario:
-
-> There is a pool with five (5) connections in use, and zero (0) idle connections. A new thread comes in requesting a connection.
+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:
-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? That available connection cannot be utilized, because the thread that could utilize it is blocked on another resource (not the pool).
+> 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?
---------------------
-Both Apache and Vibur ended the run with 45 connections, while HikariCP ended the run with 5. This has major and measurable effects for real world deployments. That is 40 additional connections that are not available to other applications, and 40 additional threads and associated memory structures in the database.
+Both Apache DBCP2 and Vibur ended the run with 45 connections, Apache Tomcat (inexplicably) with 40 connections, while HikariCP ended the run with 5 (technically six, see below). This has major and measurable effects for real world deployments. That is 35-40 additional connections that are not available to other applications, and 35-40 additional threads and associated memory structures in the database.
We know what you are thinking, *"What if the load had been sustained?"*&nbsp;&nbsp;The answer is: HikariCP also would have ramped up.
In point of fact, as soon as the pool hit zero available connections, right around 800μs into the run, HikariCP began requesting connections to be added to the pool asynchronously. If the metrics had continued to be collected past the end of the spike -- out beyond 150ms -- you would observe that an additional connection is indeed added to the pool. But *only one*, because HikariCP employs *elision logic*; at that point HikariCP would also realize that there is actually no more pending demand, and the remaining connection acquisitions would be elided.
-### Conclusion
-This scenario represents only *one* of many access patterns. HikariCP will continue to research *and innovate* in the connection pool space when presented with challenging problems encountered in real world deployments. As always, thank you for your patronage.
+### Epilog
+This scenario represents only *one* of many access patterns. HikariCP will continue to research *and innovate* when presented with challenging problems encountered in real world deployments. As always, thank you for your patronage.
diff --git a/logo.png b/logo.png
new file mode 100644
index 0000000..788fb55
--- /dev/null
+++ b/logo.png
Binary files differ
diff --git a/pom.xml b/pom.xml
index 7f846d2..5305231 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,9 +1,34 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <sureFireOptions9 />
+ <sureFireForks9>false</sureFireForks9>
+ <artifact.classifier />
+
+ <felix.bundle.plugin.version>3.3.0</felix.bundle.plugin.version>
+ <felix.version>5.6.2</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>
+ <pax.exam.version>4.11.0</pax.exam.version>
+ <pax.url.version>2.5.2</pax.url.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>
+ <junit.version>4.12</junit.version>
+ </properties>
+
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
- <version>2.6.0</version>
+ <version>2.7.1</version>
<packaging>bundle</packaging>
<name>HikariCP</name>
@@ -19,7 +44,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.6.0</tag>
+ <tag>HikariCP-2.7.1</tag>
</scm>
<licenses>
@@ -37,27 +62,6 @@
</developer>
</developers>
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-
- <felix.bundle.plugin.version>3.2.0</felix.bundle.plugin.version>
- <felix.version>5.6.1</felix.version>
- <hibernate.version>5.2.5.Final</hibernate.version>
- <javassist.version>3.21.0-GA</javassist.version>
- <jndi.version>0.11.4.1</jndi.version>
- <maven.release.version>2.5.3</maven.release.version>
- <metrics.version>3.1.2</metrics.version>
- <simpleclient.version>0.0.19</simpleclient.version>
- <mockito.version>2.3.0</mockito.version>
- <pax.exam.version>4.9.2</pax.exam.version>
- <pax.url.version>2.5.2</pax.url.version>
- <slf4j.version>1.7.21</slf4j.version>
- <log4j.version>2.7</log4j.version>
- <commons.csv.version>1.4</commons.csv.version>
- <h2.version>1.4.193</h2.version>
- <junit.version>4.12</junit.version>
- </properties>
-
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
@@ -123,6 +127,18 @@
<optional>true</optional>
</dependency>
<dependency>
+ <groupId>io.micrometer</groupId>
+ <artifactId>micrometer-core</artifactId>
+ <version>${micrometer.version}</version>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.postgresql</groupId>
+ <artifactId>postgresql</artifactId>
+ <version>42.1.4.jre7</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
@@ -194,6 +210,12 @@
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-link-assembly</artifactId>
+ <version>${pax.exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-link-mvn</artifactId>
<version>${pax.exam.version}</version>
<scope>test</scope>
@@ -224,7 +246,7 @@
<!-- Generate proxies -->
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
- <version>1.5.0</version>
+ <version>1.6.0</version>
<executions>
<execution>
<phase>compile</phase>
@@ -242,7 +264,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
- <version>0.7.7.201606060606</version>
+ <version>0.7.9</version>
<executions>
<!-- Prepares the property pointing to the JaCoCo runtime agent which is passed as VM argument when Maven the Surefire plugin is executed. -->
<execution>
@@ -256,6 +278,7 @@
<!-- Sets the name of the property containing the settings for JaCoCo runtime agent. -->
<propertyName>surefireArgLine</propertyName>
<excludes>
+ <exclude>**/com/zaxxer/hikari/util/JavassistProxyFactory*</exclude>
<exclude>**/com/zaxxer/hikari/pool/HikariProxy*</exclude>
<exclude>**/com/zaxxer/hikari/metrics/**</exclude>
</excludes>
@@ -288,6 +311,7 @@
<version>${felix.bundle.plugin.version}</version>
<extensions>true</extensions>
<configuration>
+ <classifier>${artifact.classifier}</classifier>
<instructions>
<Bundle-Name>HikariCP</Bundle-Name>
<Export-Package>
@@ -310,6 +334,7 @@
javax.sql.rowset.spi,
com.codahale.metrics;resolution:=optional,
com.codahale.metrics.health;resolution:=optional,
+ io.micrometer.core.instrument;resolution:=optional,
org.slf4j;version="[1.6,2)",
org.hibernate;resolution:=optional,
org.hibernate.cfg;resolution:=optional,
@@ -337,7 +362,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
- <version>3.6.0</version>
+ <version>3.6.1</version>
<extensions>true</extensions>
<configuration>
<source>1.8</source>
@@ -358,13 +383,13 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
- <version>2.19.1</version>
+ <version>2.20</version>
<configuration>
<!-- Sets the VM argument line used when unit tests are run. -->
- <argLine>${surefireArgLine}</argLine>
+ <argLine>${surefireArgLine} ${sureFireOptions9}</argLine>
<!-- Skips unit tests if the value of skip.unit.tests property is true -->
<skipTests>${skip.unit.tests}</skipTests>
- <reuseForks>false</reuseForks>
+ <reuseForks>${sureFireForks9}</reuseForks>
</configuration>
</plugin>
@@ -393,7 +418,7 @@
<version>2.10.4</version>
<configuration>
<show>public</show>
- <!-- excludePackageNames>com.zaxxer.hikari.*</excludePackageNames -->
+ <excludePackageNames>com.zaxxer.hikari.hibernate:com.zaxxer.hikari.metrics.*:com.zaxxer.hikari.pool:com.zaxxer.hikari.util</excludePackageNames>
<attach>true</attach>
<maxmemory>1024m</maxmemory>
</configuration>
@@ -413,6 +438,23 @@
<profiles>
<profile>
+ <id>Java9</id>
+ <activation>
+ <jdk>[9,)</jdk>
+ </activation>
+ <properties>
+ <!-- sureFireOptions9>
+ -add-opens java.base/java.net=ALL-UNNAMED
+ -add-opens java.base/java.security=ALL-UNNAMED
+ -add-exports java.base/sun.net.www.protocol.http=ALL-UNNAMED
+ -add-exports java.base/sun.net.www.protocol.https=ALL-UNNAMED
+ </sureFireOptions9 -->
+ <sureFireForks9>true</sureFireForks9>
+ <artifact.classifier>java9</artifact.classifier>
+ </properties>
+ </profile>
+
+ <profile>
<id>coverage</id>
<build>
<plugins>
diff --git a/src/main/java/com/zaxxer/hikari/HikariConfig.java b/src/main/java/com/zaxxer/hikari/HikariConfig.java
index ef9f9be..f69f82e 100644
--- a/src/main/java/com/zaxxer/hikari/HikariConfig.java
+++ b/src/main/java/com/zaxxer/hikari/HikariConfig.java
@@ -16,10 +16,15 @@
package com.zaxxer.hikari;
-import static com.zaxxer.hikari.util.UtilityElf.getNullIfEmpty;
-import static java.util.concurrent.TimeUnit.MINUTES;
-import static java.util.concurrent.TimeUnit.SECONDS;
+import com.codahale.metrics.health.HealthCheckRegistry;
+import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
+import com.zaxxer.hikari.util.PropertyElf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.sql.DataSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -33,18 +38,11 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
-import javax.naming.InitialContext;
-import javax.naming.NamingException;
-import javax.sql.DataSource;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.codahale.metrics.MetricRegistry;
-import com.codahale.metrics.health.HealthCheckRegistry;
-import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
-import com.zaxxer.hikari.util.PropertyElf;
+import static com.zaxxer.hikari.util.UtilityElf.getNullIfEmpty;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.concurrent.TimeUnit.SECONDS;
+@SuppressWarnings({"SameParameterValue", "unused"})
public class HikariConfig implements HikariConfigMXBean
{
private static final Logger LOGGER = LoggerFactory.getLogger(HikariConfig.class);
@@ -55,7 +53,7 @@ public class HikariConfig implements HikariConfigMXBean
private static final long MAX_LIFETIME = MINUTES.toMillis(30);
private static final int DEFAULT_POOL_SIZE = 10;
- private static boolean unitTest;
+ private static boolean unitTest = false;
// Properties changeable at runtime through the MBean
//
@@ -79,6 +77,7 @@ public class HikariConfig implements HikariConfigMXBean
private String jdbcUrl;
private String password;
private String poolName;
+ private String schema;
private String transactionIsolationName;
private String username;
private boolean isAutoCommit;
@@ -176,8 +175,7 @@ public class HikariConfig implements HikariConfigMXBean
/**
* Set the SQL query to be executed to test the validity of connections. Using
* the JDBC4 <code>Connection.isValid()</code> method to test connection validity can
- * be more efficient on some databases and is recommended. See
- * {@link HikariConfig#setJdbc4ConnectionTest(boolean)}.
+ * be more efficient on some databases and is recommended.
*
* @param connectionTestQuery a SQL query string
*/
@@ -313,13 +311,32 @@ public class HikariConfig implements HikariConfigMXBean
public void setDriverClassName(String driverClassName)
{
+ Class<?> driverClass = null;
+ try {
+ driverClass = this.getClass().getClassLoader().loadClass(driverClassName);
+ LOGGER.debug("Driver class found in the HikariConfig class classloader {}", this.getClass().getClassLoader());
+ } catch (ClassNotFoundException e) {
+ ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader();
+ if (threadContextClassLoader != null && threadContextClassLoader != this.getClass().getClassLoader()) {
+ try {
+ driverClass = threadContextClassLoader.loadClass(driverClassName);
+ LOGGER.debug("Driver class found in Thread context class loader {}", threadContextClassLoader);
+ } catch (ClassNotFoundException e1) {
+ LOGGER.error("Failed to load class of driverClassName {} in either of HikariConfig class classloader {} or Thread context classloader {}", driverClassName, this.getClass().getClassLoader(), threadContextClassLoader);
+ }
+ } else {
+ LOGGER.error("Failed to load class of driverClassName {} in HikariConfig class classloader {}", driverClassName, this.getClass().getClassLoader());
+ }
+ }
+ if (driverClass == null) {
+ throw new RuntimeException("Failed to load class of driverClassName [" + driverClassName + "] in either of HikariConfig class loader or Thread context classloader");
+ }
try {
- Class<?> driverClass = this.getClass().getClassLoader().loadClass(driverClassName);
driverClass.newInstance();
this.driverClassName = driverClassName;
}
catch (Exception e) {
- throw new RuntimeException("Failed to load class of driverClassName " + driverClassName, e);
+ throw new RuntimeException("Failed to instantiate class " + driverClassName, e);
}
}
@@ -410,23 +427,32 @@ public class HikariConfig implements HikariConfigMXBean
* or when {@link HikariDataSource} is constructed using the no-arg constructor
* and {@link HikariDataSource#getConnection()} is called.
* <ul>
- * <li>Any value of zero or less will <i>not</i> block the calling thread in the
- * case that a connection cannot be obtained. The pool will start and
- * continue to try to obtain connections in the background. This can mean
- * that callers to {@code DataSource#getConnection()} may encounter
- * exceptions.</li>
* <li>Any value greater than zero will be treated as a timeout for pool initialization.
* The calling thread will be blocked from continuing until a successful connection
* to the database, or until the timeout is reached. If the timeout is reached, then
- * a {@code PoolInitializationException} will be thrown.
+ * a {@code PoolInitializationException} will be thrown. </li>
+ * <li>A value of zero will <i>not</i> prevent the pool from starting in the
+ * case that a connection cannot be obtained. However, upon start the pool will
+ * attempt to obtain a connection and validate that the {@code connectionTestQuery}
+ * and {@code connectionInitSql} are valid. If those validations fail, an exception
+ * will be thrown. If a connection cannot be obtained, the validation is skipped
+ * and the the pool will start and continue to try to obtain connections in the
+ * background. This can mean that callers to {@code DataSource#getConnection()} may
+ * encounter exceptions. </li>
+ * <li>A value less than zero will <i>not</i> bypass any connection attempt and
+ * validation during startup, and therefore the pool will start immediately. The
+ * pool will continue to try to obtain connections in the background. This can mean
+ * that callers to {@code DataSource#getConnection()} may encounter exceptions. </li>
* </ul>
- * Note that this timeout does not override the {@code connectionTimeout} or
- * {@code validationTimeout}; they will be honored before this timeout is applied. The
- * default value is one millisecond.
- *
+ * Note that if this timeout value is greater than or equal to zero (0), and therefore an
+ * initial connection validation is performed, this timeout does not override the
+ * {@code connectionTimeout} or {@code validationTimeout}; they will be honored before this
+ * timeout is applied. The default value is one millisecond.
+ *
* @param initializationFailTimeout the number of milliseconds before the
- * pool initialization fails, or 0 or less to skip the initialization
- * check.
+ * pool initialization fails, or 0 to validate connection setup but continue with
+ * pool start, or less than zero to skip all initialization checks and start the
+ * pool without delay.
*/
public void setInitializationFailTimeout(long initializationFailTimeout)
{
@@ -457,8 +483,8 @@ public class HikariConfig implements HikariConfigMXBean
public void setInitializationFailFast(boolean failFast)
{
LOGGER.warn("The initializationFailFast propery is deprecated, see initializationFailTimeout");
-
- initializationFailTimeout = (failFast ? 1 : 0);
+
+ initializationFailTimeout = (failFast ? 1 : -1);
}
public boolean isIsolateInternalQueries()
@@ -519,24 +545,31 @@ public class HikariConfig implements HikariConfigMXBean
}
if (metricRegistry != null) {
- if (metricRegistry instanceof String) {
- try {
- InitialContext initCtx = new InitialContext();
- metricRegistry = initCtx.lookup((String) metricRegistry);
- }
- catch (NamingException e) {
- throw new IllegalArgumentException(e);
- }
- }
+ metricRegistry = getObjectOrPerformJndiLookup(metricRegistry);
- if (!(metricRegistry instanceof MetricRegistry)) {
- throw new IllegalArgumentException("Class must be an instance of com.codahale.metrics.MetricRegistry");
+ if (!(metricRegistry.getClass().getName().contains("MetricRegistry"))
+ && !(metricRegistry.getClass().getName().contains("MeterRegistry"))) {
+ throw new IllegalArgumentException("Class must be instance of com.codahale.metrics.MetricRegistry or io.micrometer.core.instrument.MeterRegistry");
}
}
this.metricRegistry = metricRegistry;
}
+ private Object getObjectOrPerformJndiLookup(Object object)
+ {
+ if (object instanceof String) {
+ try {
+ InitialContext initCtx = new InitialContext();
+ return initCtx.lookup((String) object);
+ }
+ catch (NamingException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+ return object;
+ }
+
/**
* Get the Codahale HealthCheckRegistry, could be null.
*
@@ -555,15 +588,7 @@ public class HikariConfig implements HikariConfigMXBean
public void setHealthCheckRegistry(Object healthCheckRegistry)
{
if (healthCheckRegistry != null) {
- if (healthCheckRegistry instanceof String) {
- try {
- InitialContext initCtx = new InitialContext();
- healthCheckRegistry = initCtx.lookup((String) healthCheckRegistry);
- }
- catch (NamingException e) {
- throw new IllegalArgumentException(e);
- }
- }
+ healthCheckRegistry = getObjectOrPerformJndiLookup(healthCheckRegistry);
if (!(healthCheckRegistry instanceof HealthCheckRegistry)) {
throw new IllegalArgumentException("Class must be an instance of com.codahale.metrics.health.HealthCheckRegistry");
@@ -748,13 +773,29 @@ public class HikariConfig implements HikariConfigMXBean
{
this.scheduledExecutor = executor;
}
-
+
public String getTransactionIsolation()
{
return transactionIsolationName;
}
/**
+ * Get the default schema name to be set on connections.
+ *
+ * @return the default schema name
+ */
+ public String getSchema() {
+ return schema;
+ }
+
+ /**
+ * Set the default schema name to be set on connections.
+ */
+ public void setSchema(String schema) {
+ this.schema = schema;
+ }
+
+ /**
* Set the default transaction isolation level. The specified value is the
* constant name from the <code>Connection</code> class, eg.
* <code>TRANSACTION_REPEATABLE_READ</code>.
@@ -807,6 +848,7 @@ public class HikariConfig implements HikariConfigMXBean
this.threadFactory = threadFactory;
}
+ @SuppressWarnings("StatementWithEmptyBody")
public void validate()
{
if (poolName == null) {
@@ -843,7 +885,8 @@ public class HikariConfig implements HikariConfigMXBean
LOGGER.warn("{} - using dataSourceClassName and ignoring jdbcUrl.", poolName);
}
}
- else if (jdbcUrl != null) {
+ else if (jdbcUrl != null || dataSourceJndiName != null) {
+ // ok
}
else if (driverClassName != null) {
LOGGER.error("{} - jdbcUrl is required with driverClassName.", poolName);
@@ -904,6 +947,7 @@ public class HikariConfig implements HikariConfigMXBean
}
}
+ @SuppressWarnings("StatementWithEmptyBody")
private void logConfiguration()
{
LOGGER.debug("{} - configuration:", poolName);
@@ -926,6 +970,9 @@ public class HikariConfig implements HikariConfigMXBean
else if (prop.matches("scheduledExecutorService|threadFactory") && value == null) {
value = "internal";
}
+ else if (prop.contains("jdbcUrl") && value instanceof String) {
+ value = ((String)value).replaceAll("([?&;]password=)[^&#;]*(.*)", "$1<masked>$2");
+ }
else if (prop.contains("password")) {
value = "<masked>";
}
@@ -938,12 +985,12 @@ public class HikariConfig implements HikariConfigMXBean
LOGGER.debug((prop + "................................................").substring(0, 32) + value);
}
catch (Exception e) {
- continue;
+ // continue
}
}
}
- protected void loadProperties(String propertyFileName)
+ private void loadProperties(String propertyFileName)
{
final File propFile = new File(propertyFileName);
try (final InputStream is = propFile.isFile() ? new FileInputStream(propFile) : this.getClass().getResourceAsStream(propertyFileName)) {
diff --git a/src/main/java/com/zaxxer/hikari/HikariDataSource.java b/src/main/java/com/zaxxer/hikari/HikariDataSource.java
index 29b8953..bc0f16c 100644
--- a/src/main/java/com/zaxxer/hikari/HikariDataSource.java
+++ b/src/main/java/com/zaxxer/hikari/HikariDataSource.java
@@ -264,6 +264,28 @@ public class HikariDataSource extends HikariConfig implements DataSource, Closea
}
/**
+ * Get the {@code HikariPoolMXBean} for this HikariDataSource instance. If this method is called on
+ * a {@code HikariDataSource} that has been constructed without a {@code HikariConfig} instance,
+ * and before an initial call to {@code #getConnection()}, the return value will be {@code null}.
+ *
+ * @return the {@code HikariPoolMXBean} instance, or {@code null}.
+ */
+ public HikariPoolMXBean getHikariPoolMXBean()
+ {
+ return pool;
+ }
+
+ /**
+ * Get the {@code HikariConfigMXBean} for this HikariDataSource instance.
+ *
+ * @return the {@code HikariConfigMXBean} instance.
+ */
+ public HikariConfigMXBean getHikariConfigMXBean()
+ {
+ return this;
+ }
+
+ /**
* Evict a connection from the pool. If the connection has already been closed (returned to the pool)
* this may result in a "soft" eviction; the connection will be evicted sometime in the future if it is
* currently in use. If the connection has not been closed, the eviction is immediate.
@@ -281,7 +303,11 @@ public class HikariDataSource extends HikariConfig implements DataSource, Closea
/**
* Suspend allocation of connections from the pool. All callers to <code>getConnection()</code>
* will block indefinitely until <code>resumePool()</code> is called.
+ *
+ * @deprecated Call the {@code HikariPoolMXBean#suspendPool()} method on the {@code HikariPoolMXBean}
+ * obtained by {@code #getHikariPoolMXBean()} or JMX lookup.
*/
+ @Deprecated
public void suspendPool()
{
HikariPool p;
@@ -292,7 +318,11 @@ public class HikariDataSource extends HikariConfig implements DataSource, Closea
/**
* Resume allocation of connections from the pool.
+ *
+ * @deprecated Call the {@code HikariPoolMXBean#resumePool()} method on the {@code HikariPoolMXBean}
+ * obtained by {@code #getHikariPoolMXBean()} or JMX lookup.
*/
+ @Deprecated
public void resumePool()
{
HikariPool p;
diff --git a/src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTracker.java b/src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTracker.java
new file mode 100644
index 0000000..a0839b1
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTracker.java
@@ -0,0 +1,115 @@
+package com.zaxxer.hikari.metrics.micrometer;
+
+import com.zaxxer.hikari.metrics.IMetricsTracker;
+import com.zaxxer.hikari.metrics.PoolStats;
+import io.micrometer.core.instrument.DistributionSummary;
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Timer;
+import io.micrometer.core.instrument.stats.quantile.WindowSketchQuantiles;
+
+import java.util.concurrent.TimeUnit;
+
+import static io.micrometer.core.instrument.stats.hist.CumulativeHistogram.buckets;
+import static io.micrometer.core.instrument.stats.hist.CumulativeHistogram.linear;
+
+public class MicrometerMetricsTracker implements IMetricsTracker
+{
+ private static final String METRIC_CATEGORY = "pool";
+ private static final String METRIC_NAME_WAIT = "Wait";
+ private static final String METRIC_NAME_USAGE = "Usage";
+ private static final String METRIC_NAME_CONNECT = "ConnectionCreation";
+ private static final String METRIC_NAME_TIMEOUT_RATE = "ConnectionTimeoutRate";
+ private static final String METRIC_NAME_TOTAL_CONNECTIONS = "TotalConnections";
+ private static final String METRIC_NAME_IDLE_CONNECTIONS = "IdleConnections";
+ private static final String METRIC_NAME_ACTIVE_CONNECTIONS = "ActiveConnections";
+ private static final String METRIC_NAME_PENDING_CONNECTIONS = "PendingConnections";
+
+ private final Timer connectionObtainTimer;
+ private final DistributionSummary connectionTimeoutMeter;
+ private final DistributionSummary connectionUsage;
+ private final DistributionSummary connectionCreation;
+ @SuppressWarnings({"FieldCanBeLocal", "unused"})
+ private final Gauge totalConnectionGauge;
+ @SuppressWarnings({"FieldCanBeLocal", "unused"})
+ private final Gauge idleConnectionGauge;
+ @SuppressWarnings({"FieldCanBeLocal", "unused"})
+ private final Gauge activeConnectionGauge;
+ @SuppressWarnings({"FieldCanBeLocal", "unused"})
+ private final Gauge pendingConnectionGauge;
+
+ MicrometerMetricsTracker(final String poolName, final PoolStats poolStats, final MeterRegistry meterRegistry)
+ {
+ this.connectionObtainTimer = meterRegistry
+ .timerBuilder(METRIC_NAME_WAIT)
+ .tags(METRIC_CATEGORY, poolName)
+ .create();
+
+ this.connectionCreation = meterRegistry
+ .summaryBuilder(METRIC_NAME_CONNECT)
+ .tags(METRIC_CATEGORY, poolName)
+ .quantiles(WindowSketchQuantiles.quantiles(0.5, 0.95).create())
+ .histogram(buckets(linear(0, 10, 20), TimeUnit.MILLISECONDS))
+ .create();
+
+ this.connectionUsage = meterRegistry
+ .summaryBuilder(METRIC_NAME_USAGE)
+ .tags(METRIC_CATEGORY, poolName)
+ .quantiles(WindowSketchQuantiles.quantiles(0.5, 0.95).create())
+ .histogram(buckets(linear(0, 10, 20), TimeUnit.MILLISECONDS))
+ .create();
+
+ this.connectionTimeoutMeter = meterRegistry
+ .summaryBuilder(METRIC_NAME_TIMEOUT_RATE)
+ .tags(METRIC_CATEGORY, poolName)
+ .quantiles(WindowSketchQuantiles.quantiles(0.5, 0.95).create())
+ .histogram(buckets(linear(0, 10, 20), TimeUnit.MILLISECONDS))
+ .create();
+
+ this.totalConnectionGauge = meterRegistry
+ .gaugeBuilder(METRIC_NAME_TOTAL_CONNECTIONS, Integer.class, (i) -> poolStats.getTotalConnections())
+ .tags(METRIC_CATEGORY, poolName)
+ .create();
+
+ this.idleConnectionGauge = meterRegistry
+ .gaugeBuilder(METRIC_NAME_IDLE_CONNECTIONS, Integer.class, (i) -> poolStats.getIdleConnections())
+ .tags(METRIC_CATEGORY, poolName)
+ .create();
+
+ this.activeConnectionGauge = meterRegistry
+ .gaugeBuilder(METRIC_NAME_ACTIVE_CONNECTIONS, Integer.class, (i) -> poolStats.getActiveConnections())
+ .tags(METRIC_CATEGORY, poolName)
+ .create();
+
+ this.pendingConnectionGauge = meterRegistry
+ .gaugeBuilder(METRIC_NAME_PENDING_CONNECTIONS, Integer.class, (i) -> poolStats.getPendingThreads())
+ .tags(METRIC_CATEGORY, poolName)
+ .create();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void recordConnectionAcquiredNanos(final long elapsedAcquiredNanos)
+ {
+ connectionObtainTimer.record(elapsedAcquiredNanos, TimeUnit.NANOSECONDS);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void recordConnectionUsageMillis(final long elapsedBorrowedMillis)
+ {
+ connectionUsage.record(elapsedBorrowedMillis);
+ }
+
+ @Override
+ public void recordConnectionTimeout()
+ {
+ connectionTimeoutMeter.count();
+ }
+
+ @Override
+ public void recordConnectionCreatedMillis(long connectionCreatedMillis)
+ {
+ connectionCreation.record(connectionCreatedMillis);
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerFactory.java b/src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerFactory.java
new file mode 100644
index 0000000..4072927
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerFactory.java
@@ -0,0 +1,22 @@
+package com.zaxxer.hikari.metrics.micrometer;
+
+import com.zaxxer.hikari.metrics.IMetricsTracker;
+import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
+import com.zaxxer.hikari.metrics.PoolStats;
+import io.micrometer.core.instrument.MeterRegistry;
+
+public class MicrometerMetricsTrackerFactory implements MetricsTrackerFactory {
+
+ private final MeterRegistry registry;
+
+ public MicrometerMetricsTrackerFactory(MeterRegistry registry)
+ {
+ this.registry = registry;
+ }
+
+ @Override
+ public IMetricsTracker create(String poolName, PoolStats poolStats)
+ {
+ return new MicrometerMetricsTracker(poolName, poolStats, registry);
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java b/src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java
index 3bff974..3d5fcf0 100644
--- a/src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java
+++ b/src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java
@@ -18,38 +18,46 @@ package com.zaxxer.hikari.metrics.prometheus;
import com.zaxxer.hikari.metrics.PoolStats;
import io.prometheus.client.Collector;
-
+import io.prometheus.client.GaugeMetricFamily;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
class HikariCPCollector extends Collector {
- private final PoolStats poolStats;
- private final List<String> labelNames;
- private final List<String> labelValues;
-
- HikariCPCollector(String poolName, PoolStats poolStats) {
- this.poolStats = poolStats;
- this.labelNames = Collections.singletonList("pool");
- this.labelValues = Collections.singletonList(poolName);
- }
+
+ private static final List<String> LABEL_NAMES = Collections.singletonList("pool");
+
+ private final Map<String, PoolStats> poolStatsMap = new ConcurrentHashMap<>();
@Override
public List<MetricFamilySamples> collect() {
return Arrays.asList(
- createSample("hikaricp_active_connections", "Active connections", poolStats.getActiveConnections()),
- createSample("hikaricp_idle_connections", "Idle connections", poolStats.getIdleConnections()),
- createSample("hikaricp_pending_threads", "Pending threads", poolStats.getPendingThreads()),
- createSample("hikaricp_connections", "The number of current connections", poolStats.getTotalConnections())
+ createGauge("hikaricp_active_connections", "Active connections",
+ PoolStats::getActiveConnections),
+ createGauge("hikaricp_idle_connections", "Idle connections",
+ PoolStats::getIdleConnections),
+ createGauge("hikaricp_pending_threads", "Pending threads",
+ PoolStats::getPendingThreads),
+ createGauge("hikaricp_connections", "The number of current connections",
+ PoolStats::getTotalConnections)
);
}
- private MetricFamilySamples createSample(String name, String helpMessage, double value)
- {
- List<MetricFamilySamples.Sample> samples = Collections.singletonList(
- new MetricFamilySamples.Sample(name, labelNames, labelValues, value)
- );
+ protected HikariCPCollector add(String name, PoolStats poolStats) {
+ poolStatsMap.put(name, poolStats);
+ return this;
+ }
- return new MetricFamilySamples(name, Type.GAUGE, helpMessage, samples);
+ private GaugeMetricFamily createGauge(String metric, String help,
+ Function<PoolStats, Integer> metricValueFunction) {
+ GaugeMetricFamily metricFamily = new GaugeMetricFamily(metric, help, LABEL_NAMES);
+ poolStatsMap.forEach((k, v) -> metricFamily.addMetric(
+ Collections.singletonList(k),
+ metricValueFunction.apply(v)
+ ));
+ return metricFamily;
}
}
diff --git a/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java
index 0bd54fc..ae856a5 100644
--- a/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java
+++ b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java
@@ -17,90 +17,68 @@
package com.zaxxer.hikari.metrics.prometheus;
import com.zaxxer.hikari.metrics.IMetricsTracker;
-
import io.prometheus.client.Collector;
import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.Counter;
import io.prometheus.client.Summary;
+import java.util.concurrent.TimeUnit;
class PrometheusMetricsTracker implements IMetricsTracker
{
- private final Counter.Child connectionTimeoutCounter;
- private final Summary.Child elapsedAcquiredSummary;
- private final Summary.Child elapsedBorrowedSummary;
- private final Summary.Child elapsedCreationSummary;
-
- private final Counter ctCounter;
- private final Summary eaSummary;
- private final Summary ebSummary;
- private final Summary ecSummary;
- private final Collector collector;
-
- PrometheusMetricsTracker(String poolName, Collector collector)
- {
- this.collector = collector;
-
- ctCounter = Counter.build()
- .name("hikaricp_connection_timeout_count")
- .labelNames("pool")
- .help("Connection timeout count")
- .register();
-
- this.connectionTimeoutCounter = ctCounter.labels(poolName);
-
- eaSummary = Summary.build()
- .name("hikaricp_connection_acquired_nanos")
- .labelNames("pool")
- .help("Connection acquired time (ns)")
- .register();
- this.elapsedAcquiredSummary = eaSummary.labels(poolName);
-
- ebSummary = Summary.build()
- .name("hikaricp_connection_usage_millis")
- .labelNames("pool")
- .help("Connection usage (ms)")
- .register();
- this.elapsedBorrowedSummary = ebSummary.labels(poolName);
-
- ecSummary = Summary.build()
- .name("hikaricp_connection_creation_millis")
- .labelNames("pool")
- .help("Connection creation (ms)")
- .register();
- this.elapsedCreationSummary = ecSummary.labels(poolName);
- }
-
- @Override
- public void close()
- {
- CollectorRegistry.defaultRegistry.unregister(ctCounter);
- CollectorRegistry.defaultRegistry.unregister(eaSummary);
- CollectorRegistry.defaultRegistry.unregister(ebSummary);
- CollectorRegistry.defaultRegistry.unregister(ecSummary);
- CollectorRegistry.defaultRegistry.unregister(collector);
+ private static final Counter CONNECTION_TIMEOUT_COUNTER = Counter.build()
+ .name("hikaricp_connection_timeout_total")
+ .labelNames("pool")
+ .help("Connection timeout total count")
+ .register();
+ private static final Summary ELAPSED_ACQUIRED_SUMMARY = Summary.build()
+ .name("hikaricp_connection_acquired_nanos")
+ .labelNames("pool")
+ .help("Connection acquired time (ns)")
+ .register();
+ private static final Summary ELAPSED_BORROWED_SUMMARY = Summary.build()
+ .name("hikaricp_connection_usage_millis")
+ .labelNames("pool")
+ .help("Connection usage (ms)")
+ .register();
+ private static final Summary ELAPSED_CREATION_SUMMARY = Summary.build()
+ .name("hikaricp_connection_creation_millis")
+ .labelNames("pool")
+ .help("Connection creation (ms)")
+ .register();
+
+ private final Counter.Child connectionTimeoutCounterChild;
+ private final Summary.Child elapsedAcquiredSummaryChild;
+ private final Summary.Child elapsedBorrowedSummaryChild;
+ private final Summary.Child elapsedCreationSummaryChild;
+
+ PrometheusMetricsTracker(String poolName) {
+ this.connectionTimeoutCounterChild = CONNECTION_TIMEOUT_COUNTER.labels(poolName);
+ this.elapsedAcquiredSummaryChild = ELAPSED_ACQUIRED_SUMMARY.labels(poolName);
+ this.elapsedBorrowedSummaryChild = ELAPSED_BORROWED_SUMMARY.labels(poolName);
+ this.elapsedCreationSummaryChild = ELAPSED_CREATION_SUMMARY.labels(poolName);
}
@Override
public void recordConnectionAcquiredNanos(long elapsedAcquiredNanos)
{
- elapsedAcquiredSummary.observe(elapsedAcquiredNanos);
+ elapsedAcquiredSummaryChild.observe(elapsedAcquiredNanos);
}
@Override
public void recordConnectionUsageMillis(long elapsedBorrowedMillis)
{
- elapsedBorrowedSummary.observe(elapsedBorrowedMillis);
+ elapsedBorrowedSummaryChild.observe(elapsedBorrowedMillis);
}
@Override
public void recordConnectionCreatedMillis(long connectionCreatedMillis)
{
- elapsedCreationSummary.observe(connectionCreatedMillis);
+ elapsedCreationSummaryChild.observe(connectionCreatedMillis);
}
@Override
public void recordConnectionTimeout()
{
- connectionTimeoutCounter.inc();
+ connectionTimeoutCounterChild.inc();
}
}
diff --git a/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java
index 2a0b5c3..0d18a35 100644
--- a/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java
+++ b/src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java
@@ -20,20 +20,29 @@ import com.zaxxer.hikari.metrics.IMetricsTracker;
import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
import com.zaxxer.hikari.metrics.PoolStats;
-import io.prometheus.client.Collector;
-
/**
* <pre>{@code
* HikariConfig config = new HikariConfig();
* config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
* }</pre>
*/
-public class PrometheusMetricsTrackerFactory implements MetricsTrackerFactory
-{
+public class PrometheusMetricsTrackerFactory implements MetricsTrackerFactory {
+
+ private static HikariCPCollector collector;
+
@Override
- public IMetricsTracker create(String poolName, PoolStats poolStats)
- {
- Collector collector = new HikariCPCollector(poolName, poolStats).register();
- return new PrometheusMetricsTracker(poolName, collector);
+ public IMetricsTracker create(String poolName, PoolStats poolStats) {
+ getCollector().add(poolName, poolStats);
+ return new PrometheusMetricsTracker(poolName);
+ }
+
+ /**
+ * initialize and register collector if it isn't initialized yet
+ */
+ private HikariCPCollector getCollector() {
+ if (collector == null) {
+ collector = new HikariCPCollector().register();
+ }
+ return collector;
}
}
diff --git a/src/main/java/com/zaxxer/hikari/pool/HikariPool.java b/src/main/java/com/zaxxer/hikari/pool/HikariPool.java
index e9b7364..94e4e43 100755
--- a/src/main/java/com/zaxxer/hikari/pool/HikariPool.java
+++ b/src/main/java/com/zaxxer/hikari/pool/HikariPool.java
@@ -16,11 +16,6 @@
package com.zaxxer.hikari.pool;
-import static java.util.Collections.unmodifiableCollection;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static java.util.concurrent.TimeUnit.SECONDS;
-
-import static com.zaxxer.hikari.pool.PoolEntry.LASTACCESS_REVERSE_COMPARABLE;
import static com.zaxxer.hikari.util.ClockSource.currentTime;
import static com.zaxxer.hikari.util.ClockSource.elapsedDisplayString;
import static com.zaxxer.hikari.util.ClockSource.elapsedMillis;
@@ -29,17 +24,19 @@ import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_IN_
import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_NOT_IN_USE;
import static com.zaxxer.hikari.util.UtilityElf.createThreadPoolExecutor;
import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
+import static java.util.Collections.unmodifiableCollection;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
import java.sql.Connection;
import java.sql.SQLException;
-import java.sql.SQLTimeoutException;
import java.sql.SQLTransientConnectionException;
import java.util.Collection;
+import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
@@ -48,6 +45,8 @@ import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.ThreadPoolExecutor;
+import com.zaxxer.hikari.metrics.micrometer.MicrometerMetricsTrackerFactory;
+import io.micrometer.core.instrument.MeterRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -84,13 +83,14 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
private final long HOUSEKEEPING_PERIOD_MS = Long.getLong("com.zaxxer.hikari.housekeeping.periodMs", SECONDS.toMillis(30));
private final PoolEntryCreator POOL_ENTRY_CREATOR = new PoolEntryCreator(null);
+ private final PoolEntryCreator POST_FILL_POOL_ENTRY_CREATOR = new PoolEntryCreator("After adding ");
private final Collection<Runnable> addConnectionQueue;
private final ThreadPoolExecutor addConnectionExecutor;
private final ThreadPoolExecutor closeConnectionExecutor;
private final ConcurrentBag<PoolEntry> connectionBag;
- private final ProxyLeakTask leakTask;
+ private final ProxyLeakTaskFactory leakTaskFactory;
private final SuspendResumeLock suspendResumeLock;
private ScheduledExecutorService houseKeepingExecutorService;
@@ -108,7 +108,7 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
this.connectionBag = new ConcurrentBag<>(this);
this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
- initializeHouseKeepingExecutorService();
+ this.houseKeepingExecutorService = initializeHouseKeepingExecutorService();
checkFailFast();
@@ -130,9 +130,9 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
this.addConnectionExecutor = createThreadPoolExecutor(addConnectionQueue, poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardPolicy());
this.closeConnectionExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), poolName + " connection closer", threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
- this.leakTask = new ProxyLeakTask(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
+ this.leakTaskFactory = new ProxyLeakTaskFactory(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
- this.houseKeeperTask = this.houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, HOUSEKEEPING_PERIOD_MS, MILLISECONDS);
+ this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, HOUSEKEEPING_PERIOD_MS, MILLISECONDS);
}
/**
@@ -175,9 +175,11 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
}
else {
metricsTracker.recordBorrowStats(poolEntry, startTime);
- return poolEntry.createProxyConnection(leakTask.schedule(poolEntry), now);
+ return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);
}
} while (timeout > 0L);
+
+ metricsTracker.recordBorrowTimeoutStats(startTime);
}
catch (InterruptedException e) {
if (poolEntry != null) {
@@ -219,7 +221,7 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
softEvictConnections();
addConnectionExecutor.shutdown();
- addConnectionExecutor.awaitTermination(5L, SECONDS);
+ addConnectionExecutor.awaitTermination(getLoginTimeout(), SECONDS);
destroyHouseKeepingExecutorService();
@@ -232,16 +234,16 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
do {
abortActiveConnections(assassinExecutor);
softEvictConnections();
- } while (getTotalConnections() > 0 && elapsedMillis(start) < SECONDS.toMillis(5));
+ } while (getTotalConnections() > 0 && elapsedMillis(start) < SECONDS.toMillis(10));
}
finally {
assassinExecutor.shutdown();
- assassinExecutor.awaitTermination(5L, SECONDS);
+ assassinExecutor.awaitTermination(10L, SECONDS);
}
shutdownNetworkTimeoutExecutor();
closeConnectionExecutor.shutdown();
- closeConnectionExecutor.awaitTermination(5L, SECONDS);
+ closeConnectionExecutor.awaitTermination(10L, SECONDS);
}
finally {
logPoolState("After shutdown ");
@@ -270,9 +272,12 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
public void setMetricRegistry(Object metricRegistry)
{
- if (metricRegistry != null) {
+ if (metricRegistry instanceof MetricRegistry) {
setMetricsTrackerFactory(new CodahaleMetricsTrackerFactory((MetricRegistry) metricRegistry));
}
+ else if (metricRegistry instanceof MeterRegistry) {
+ setMetricsTrackerFactory(new MicrometerMetricsTrackerFactory((MeterRegistry) metricRegistry));
+ }
else {
setMetricsTrackerFactory(null);
}
@@ -301,14 +306,14 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
/** {@inheritDoc} */
@Override
- public Future<Boolean> addBagItem(final int waiting)
+ public void addBagItem(final int waiting)
{
final boolean shouldAdd = waiting - addConnectionQueue.size() >= 0; // Yes, >= is intentional.
if (shouldAdd) {
- return addConnectionExecutor.submit(POOL_ENTRY_CREATOR);
+ addConnectionExecutor.submit(POOL_ENTRY_CREATOR);
}
- return CompletableFuture.completedFuture(Boolean.TRUE);
+ CompletableFuture.completedFuture(Boolean.TRUE);
}
// ***********************************************************************
@@ -424,6 +429,7 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
}
}
+ @SuppressWarnings("unused")
int[] getPoolStateCounts()
{
return connectionBag.getStateCounts();
@@ -447,7 +453,11 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
final long variance = maxLifetime > 10_000 ? ThreadLocalRandom.current().nextLong( maxLifetime / 40 ) : 0;
final long lifetime = maxLifetime - variance;
poolEntry.setFutureEol(houseKeepingExecutorService.schedule(
- () -> softEvictConnection(poolEntry, "(connection has passed maxLifetime)", false /* not owner */),
+ () -> {
+ if (softEvictConnection(poolEntry, "(connection has passed maxLifetime)", false /* not owner */)) {
+ addBagItem(connectionBag.getWaitingThreadCount());
+ }
+ },
lifetime, MILLISECONDS));
}
@@ -469,7 +479,7 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
final int connectionsToAdd = Math.min(config.getMaximumPoolSize() - getTotalConnections(), config.getMinimumIdle() - getIdleConnections())
- addConnectionQueue.size();
for (int i = 0; i < connectionsToAdd; i++) {
- addConnectionExecutor.submit((i < connectionsToAdd - 1) ? POOL_ENTRY_CREATOR : new PoolEntryCreator("After adding "));
+ addConnectionExecutor.submit((i < connectionsToAdd - 1) ? POOL_ENTRY_CREATOR : POST_FILL_POOL_ENTRY_CREATOR);
}
}
@@ -499,8 +509,12 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
*/
private void checkFailFast()
{
+ final long initializationTimeout = config.getInitializationFailTimeout();
+ if (initializationTimeout < 0) {
+ return;
+ }
+
final long startTime = currentTime();
- Throwable throwable = new SQLTimeoutException("HikariCP was unable to initialize connections in pool " + poolName);
do {
final PoolEntry poolEntry = createPoolEntry();
if (poolEntry != null) {
@@ -509,23 +523,21 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
LOGGER.debug("{} - Added connection {}", poolName, poolEntry.connection);
}
else {
- final Connection connection = poolEntry.close();
- quietlyCloseConnection(connection, "(initialization check complete and minimumIdle is zero)");
+ quietlyCloseConnection(poolEntry.close(), "(initialization check complete and minimumIdle is zero)");
}
return;
}
- throwable = getLastConnectionFailure();
- if (throwable instanceof ConnectionSetupException) {
- throwPoolInitializationException(throwable.getCause());
+ if (getLastConnectionFailure() instanceof ConnectionSetupException) {
+ throwPoolInitializationException(getLastConnectionFailure().getCause());
}
quietlySleep(1000L);
- } while (elapsedMillis(startTime) < config.getInitializationFailTimeout());
+ } while (elapsedMillis(startTime) < initializationTimeout);
- if (config.getInitializationFailTimeout() > 0) {
- throwPoolInitializationException(throwable);
+ if (initializationTimeout > 0) {
+ throwPoolInitializationException(getLastConnectionFailure());
}
}
@@ -536,25 +548,28 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
throw new PoolInitializationException(t);
}
- private void softEvictConnection(final PoolEntry poolEntry, final String reason, final boolean owner)
+ private boolean softEvictConnection(final PoolEntry poolEntry, final String reason, final boolean owner)
{
poolEntry.markEvicted();
if (owner || connectionBag.reserve(poolEntry)) {
closeConnection(poolEntry, reason);
+ return true;
}
+
+ return false;
}
- private void initializeHouseKeepingExecutorService()
+ private ScheduledExecutorService initializeHouseKeepingExecutorService()
{
if (config.getScheduledExecutor() == null) {
final ThreadFactory threadFactory = Optional.ofNullable(config.getThreadFactory()).orElse(new DefaultThreadFactory(poolName + " housekeeper", true));
final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, threadFactory, new ThreadPoolExecutor.DiscardPolicy());
executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
executor.setRemoveOnCancelPolicy(true);
- this.houseKeepingExecutorService = executor;
+ return executor;
}
else {
- this.houseKeepingExecutorService = config.getScheduledExecutor();
+ return config.getScheduledExecutor();
}
}
@@ -605,11 +620,11 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
*/
private final class PoolEntryCreator implements Callable<Boolean>
{
- private final String afterPrefix;
+ private final String loggingPrefix;
- PoolEntryCreator(String afterPrefix)
+ PoolEntryCreator(String loggingPrefix)
{
- this.afterPrefix = afterPrefix;
+ this.loggingPrefix = loggingPrefix;
}
@Override
@@ -621,8 +636,8 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
if (poolEntry != null) {
connectionBag.add(poolEntry);
LOGGER.debug("{} - Added connection {}", poolName, poolEntry.connection);
- if (afterPrefix != null) {
- logPoolState(afterPrefix);
+ if (loggingPrefix != null) {
+ logPoolState(loggingPrefix);
}
return Boolean.TRUE;
}
@@ -657,7 +672,7 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
// refresh timeouts in case they changed via MBean
connectionTimeout = config.getConnectionTimeout();
validationTimeout = config.getValidationTimeout();
- leakTask.updateLeakDetectionThreshold(config.getLeakDetectionThreshold());
+ leakTaskFactory.updateLeakDetectionThreshold(config.getLeakDetectionThreshold());
final long idleTimeout = config.getIdleTimeout();
final long now = currentTime();
@@ -683,14 +698,16 @@ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBag
logPoolState("Before cleanup ");
afterPrefix = "After cleanup ";
- connectionBag
- .values(STATE_NOT_IN_USE)
- .stream()
- .sorted(LASTACCESS_REVERSE_COMPARABLE)
- .skip(config.getMinimumIdle())
- .filter(p -> elapsedMillis(p.lastAccessed, now) > idleTimeout)
- .filter(p -> connectionBag.reserve(p))
- .forEachOrdered(p -> closeConnection(p, "(connection has passed idleTimeout)"));
+ final List<PoolEntry> notInUse = connectionBag.values(STATE_NOT_IN_USE);
+ int removed = 0;
+ for (PoolEntry entry : notInUse) {
+ if (elapsedMillis(entry.lastAccessed, now) > idleTimeout && connectionBag.reserve(entry)) {
+ closeConnection(entry, "(connection has passed idleTimeout)");
+ if (++removed > config.getMinimumIdle()) {
+ break;
+ }
+ }
+ }
}
logPoolState(afterPrefix);
diff --git a/src/main/java/com/zaxxer/hikari/pool/PoolBase.java b/src/main/java/com/zaxxer/hikari/pool/PoolBase.java
index b463dae..9063d81 100755
--- a/src/main/java/com/zaxxer/hikari/pool/PoolBase.java
+++ b/src/main/java/com/zaxxer/hikari/pool/PoolBase.java
@@ -16,6 +16,7 @@
package com.zaxxer.hikari.pool;
+import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_SCHEMA;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -29,12 +30,16 @@ import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.management.MBeanServer;
import javax.management.ObjectName;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
import javax.sql.DataSource;
+import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -61,11 +66,11 @@ abstract class PoolBase
protected final HikariConfig config;
protected final String poolName;
- protected long connectionTimeout;
- protected long validationTimeout;
- protected IMetricsTrackerDelegate metricsTracker;
+ long connectionTimeout;
+ long validationTimeout;
+ IMetricsTrackerDelegate metricsTracker;
- private static final String[] RESET_STATES = {"readOnly", "autoCommit", "isolation", "catalog", "netTimeout"};
+ private static final String[] RESET_STATES = {"readOnly", "autoCommit", "isolation", "catalog", "netTimeout", "schema"};
private static final int UNINITIALIZED = -1;
private static final int TRUE = 1;
private static final int FALSE = 0;
@@ -79,6 +84,7 @@ abstract class PoolBase
private DataSource dataSource;
private final String catalog;
+ private final String schema;
private final boolean isReadOnly;
private final boolean isAutoCommit;
@@ -94,6 +100,7 @@ abstract class PoolBase
this.networkTimeout = UNINITIALIZED;
this.catalog = config.getCatalog();
+ this.schema = config.getSchema();
this.isReadOnly = config.isReadOnly();
this.isAutoCommit = config.isAutoCommit();
this.transactionIsolation = UtilityElf.getTransactionIsolation(config.getTransactionIsolation());
@@ -146,28 +153,30 @@ abstract class PoolBase
{
try {
try {
+ setNetworkTimeout(connection, validationTimeout);
+
+ final long validationSeconds = (int) Math.max(1000L, validationTimeout) / 1000;
+
if (isUseJdbc4Validation) {
- return connection.isValid((int) MILLISECONDS.toSeconds(Math.max(1000L, validationTimeout)));
+ return connection.isValid((int) validationSeconds);
}
- setNetworkTimeout(connection, validationTimeout);
-
try (Statement statement = connection.createStatement()) {
if (isNetworkTimeoutSupported != TRUE) {
- setQueryTimeout(statement, (int) MILLISECONDS.toSeconds(Math.max(1000L, validationTimeout)));
+ setQueryTimeout(statement, (int) validationSeconds);
}
statement.execute(config.getConnectionTestQuery());
}
}
finally {
+ setNetworkTimeout(connection, networkTimeout);
+
if (isIsolateInternalQueries && !isAutoCommit) {
connection.rollback();
}
}
- setNetworkTimeout(connection, networkTimeout);
-
return true;
}
catch (Exception e) {
@@ -225,6 +234,11 @@ abstract class PoolBase
resetBits |= DIRTY_BIT_NETTIMEOUT;
}
+ if ((dirtyBits & DIRTY_BIT_SCHEMA) != 0 && schema != null && !schema.equals(proxyConnection.getSchemaState())) {
+ connection.setSchema(schema);
+ resetBits |= DIRTY_BIT_SCHEMA;
+ }
+
if (resetBits != 0 && LOGGER.isDebugEnabled()) {
LOGGER.debug("{} - Reset ({}) on connection {}", poolName, stringFromResetBits(resetBits), connection);
}
@@ -237,6 +251,15 @@ abstract class PoolBase
}
}
+ long getLoginTimeout()
+ {
+ try {
+ return (dataSource != null) ? dataSource.getLoginTimeout() : SECONDS.toSeconds(5);
+ } catch (SQLException e) {
+ return SECONDS.toSeconds(5);
+ }
+ }
+
// ***********************************************************************
// JMX methods
// ***********************************************************************
@@ -244,7 +267,7 @@ abstract class PoolBase
/**
* Register MBeans for HikariConfig and HikariPool.
*
- * @param pool a HikariPool instance
+ * @param hikariPool a HikariPool instance
*/
void registerMBeans(final HikariPool hikariPool)
{
@@ -300,8 +323,6 @@ abstract class PoolBase
/**
* Create/initialize the underlying DataSource.
- *
- * @return a DataSource instance
*/
private void initializeDataSource()
{
@@ -310,6 +331,7 @@ abstract class PoolBase
final String password = config.getPassword();
final String dsClassName = config.getDataSourceClassName();
final String driverClassName = config.getDriverClassName();
+ final String dataSourceJNDI = config.getDataSourceJNDI();
final Properties dataSourceProperties = config.getDataSourceProperties();
DataSource dataSource = config.getDataSource();
@@ -320,6 +342,14 @@ abstract class PoolBase
else if (jdbcUrl != null && dataSource == null) {
dataSource = new DriverDataSource(jdbcUrl, driverClassName, dataSourceProperties, username, password);
}
+ else if (dataSourceJNDI != null && dataSource == null) {
+ try {
+ InitialContext ic = new InitialContext();
+ dataSource = (DataSource) ic.lookup(dataSourceJNDI);
+ } catch (NamingException e) {
+ throw new PoolInitializationException(e);
+ }
+ }
if (dataSource != null) {
setLoginTimeout(dataSource);
@@ -334,7 +364,7 @@ abstract class PoolBase
*
* @return a Connection connection
*/
- Connection newConnection() throws Exception
+ private Connection newConnection() throws Exception
{
final long start = currentTime();
@@ -375,7 +405,7 @@ abstract class PoolBase
* Setup a connection initial state.
*
* @param connection a Connection
- * @throws SQLException thrown from driver
+ * @throws ConnectionSetupException thrown if any exception is encountered
*/
private void setupConnection(final Connection connection) throws ConnectionSetupException
{
@@ -400,6 +430,10 @@ abstract class PoolBase
connection.setCatalog(catalog);
}
+ if (schema != null) {
+ connection.setSchema(schema);
+ }
+
executeSql(connection, config.getConnectionInitSql(), true);
setNetworkTimeout(connection, networkTimeout);
@@ -438,10 +472,12 @@ abstract class PoolBase
}
catch (SQLException e) {
LOGGER.warn("{} - Default transaction isolation level detection failed ({}).", poolName, e.getMessage());
+ if (e.getSQLState() != null && !e.getSQLState().startsWith("08")) {
+ throw e;
+ }
}
- finally {
- isValidChecked = true;
- }
+
+ isValidChecked = true;
}
}
@@ -610,7 +646,7 @@ abstract class PoolBase
{
private static final long serialVersionUID = 929872118275916521L;
- public ConnectionSetupException(Throwable t)
+ ConnectionSetupException(Throwable t)
{
super(t);
}
@@ -635,12 +671,14 @@ abstract class PoolBase
}
}
- static interface IMetricsTrackerDelegate extends AutoCloseable
+ interface IMetricsTrackerDelegate extends AutoCloseable
{
default void recordConnectionUsage(PoolEntry poolEntry) {}
default void recordConnectionCreated(long connectionCreatedMillis) {}
+ default void recordBorrowTimeoutStats(long startTime) {}
+
default void recordBorrowStats(final PoolEntry poolEntry, final long startTime) {}
default void recordConnectionTimeout() {}
@@ -676,6 +714,12 @@ abstract class PoolBase
}
@Override
+ public void recordBorrowTimeoutStats(long startTime)
+ {
+ tracker.recordConnectionAcquiredNanos(elapsedNanos(startTime));
+ }
+
+ @Override
public void recordBorrowStats(final PoolEntry poolEntry, final long startTime)
{
final long now = currentTime();
diff --git a/src/main/java/com/zaxxer/hikari/pool/PoolEntry.java b/src/main/java/com/zaxxer/hikari/pool/PoolEntry.java
index 5b16047..2b45256 100644
--- a/src/main/java/com/zaxxer/hikari/pool/PoolEntry.java
+++ b/src/main/java/com/zaxxer/hikari/pool/PoolEntry.java
@@ -15,22 +15,18 @@
*/
package com.zaxxer.hikari.pool;
-import static com.zaxxer.hikari.util.ClockSource.currentTime;
-import static com.zaxxer.hikari.util.ClockSource.elapsedDisplayString;
-import static com.zaxxer.hikari.util.ClockSource.elapsedMillis;
+import com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry;
+import com.zaxxer.hikari.util.FastList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
-import java.util.Comparator;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry;
-import com.zaxxer.hikari.util.FastList;
+import static com.zaxxer.hikari.util.ClockSource.*;
/**
* Entry used in the ConcurrentBag to track Connection instances.
@@ -42,12 +38,12 @@ final class PoolEntry implements IConcurrentBagEntry
private static final Logger LOGGER = LoggerFactory.getLogger(PoolEntry.class);
private static final AtomicIntegerFieldUpdater<PoolEntry> stateUpdater;
- static final Comparator<PoolEntry> LASTACCESS_REVERSE_COMPARABLE;
-
Connection connection;
long lastAccessed;
long lastBorrowed;
- private volatile int state;
+
+ @SuppressWarnings("FieldCanBeLocal")
+ private volatile int state = 0;
private volatile boolean evict;
private volatile ScheduledFuture<?> endOfLife;
@@ -60,13 +56,6 @@ final class PoolEntry implements IConcurrentBagEntry
static
{
- LASTACCESS_REVERSE_COMPARABLE = new Comparator<PoolEntry>() {
- @Override
- public int compare(final PoolEntry entryOne, final PoolEntry entryTwo) {
- return Long.compare(entryTwo.lastAccessed, entryOne.lastAccessed);
- }
- };
-
stateUpdater = AtomicIntegerFieldUpdater.newUpdater(PoolEntry.class, "state");
}
@@ -94,7 +83,9 @@ final class PoolEntry implements IConcurrentBagEntry
}
/**
- * @param endOfLife
+ * Set the end of life {@link ScheduledFuture}.
+ *
+ * @param endOfLife this PoolEntry/Connection's end of life {@link ScheduledFuture}
*/
void setFutureEol(final ScheduledFuture<?> endOfLife)
{
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java b/src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java
index 7143bae..2d07e31 100644
--- a/src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java
@@ -19,10 +19,10 @@ package com.zaxxer.hikari.pool;
import static com.zaxxer.hikari.util.ClockSource.currentTime;
import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.CallableStatement;
import java.sql.Connection;
+import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Savepoint;
@@ -44,16 +44,18 @@ import com.zaxxer.hikari.util.FastList;
*/
public abstract class ProxyConnection implements Connection
{
- static final int DIRTY_BIT_READONLY = 0b00001;
- static final int DIRTY_BIT_AUTOCOMMIT = 0b00010;
- static final int DIRTY_BIT_ISOLATION = 0b00100;
- static final int DIRTY_BIT_CATALOG = 0b01000;
- static final int DIRTY_BIT_NETTIMEOUT = 0b10000;
+ static final int DIRTY_BIT_READONLY = 0b000001;
+ static final int DIRTY_BIT_AUTOCOMMIT = 0b000010;
+ static final int DIRTY_BIT_ISOLATION = 0b000100;
+ static final int DIRTY_BIT_CATALOG = 0b001000;
+ static final int DIRTY_BIT_NETTIMEOUT = 0b010000;
+ static final int DIRTY_BIT_SCHEMA = 0b100000;
private static final Logger LOGGER;
private static final Set<String> ERROR_STATES;
private static final Set<Integer> ERROR_CODES;
+ @SuppressWarnings("WeakerAccess")
protected Connection delegate;
private final PoolEntry poolEntry;
@@ -69,6 +71,7 @@ public abstract class ProxyConnection implements Connection
private int networkTimeout;
private int transactionIsolation;
private String dbcatalog;
+ private String dbschema;
// static initializer
static {
@@ -101,10 +104,7 @@ public abstract class ProxyConnection implements Connection
@Override
public final String toString()
{
- return new StringBuilder(64)
- .append(this.getClass().getSimpleName()).append('@').append(System.identityHashCode(this))
- .append(" wrapping ")
- .append(delegate).toString();
+ return this.getClass().getSimpleName() + '@' + System.identityHashCode(this) + " wrapping " + delegate;
}
// ***********************************************************************
@@ -121,6 +121,11 @@ public abstract class ProxyConnection implements Connection
return dbcatalog;
}
+ final String getSchemaState()
+ {
+ return dbschema;
+ }
+
final int getTransactionIsolationState()
{
return transactionIsolation;
@@ -186,29 +191,32 @@ public abstract class ProxyConnection implements Connection
leakTask.cancel();
}
- private final synchronized <T extends Statement> T trackStatement(final T statement)
+ private synchronized <T extends Statement> T trackStatement(final T statement)
{
openStatements.add(statement);
return statement;
}
- private final void closeStatements()
+ @SuppressWarnings("EmptyTryBlock")
+ private synchronized void closeStatements()
{
final int size = openStatements.size();
if (size > 0) {
for (int i = 0; i < size && delegate != ClosedConnection.CLOSED_CONNECTION; i++) {
- try (Statement statement = openStatements.get(i)) {
+ try (Statement ignored = openStatements.get(i)) {
// automatic resource cleanup
}
catch (SQLException e) {
- checkException(e);
+ LOGGER.warn("{} - Connection {} marked as broken because of an exception closing open statements during Connection.close()",
+ poolEntry.getPoolName(), delegate);
+ leakTask.cancel();
+ poolEntry.evict("(exception closing Statements during Connection.close())");
+ delegate = ClosedConnection.CLOSED_CONNECTION;
}
}
- synchronized (this) {
- openStatements.clear();
- }
+ openStatements.clear();
}
}
@@ -346,6 +354,14 @@ public abstract class ProxyConnection implements Connection
/** {@inheritDoc} */
@Override
+ public DatabaseMetaData getMetaData() throws SQLException
+ {
+ markCommitStateDirty();
+ return delegate.getMetaData();
+ }
+
+ /** {@inheritDoc} */
+ @Override
public void commit() throws SQLException
{
delegate.commit();
@@ -419,6 +435,15 @@ public abstract class ProxyConnection implements Connection
/** {@inheritDoc} */
@Override
+ public void setSchema(String schema) throws SQLException
+ {
+ delegate.setSchema(schema);
+ dbschema = schema;
+ dirtyBits |= DIRTY_BIT_SCHEMA;
+ }
+
+ /** {@inheritDoc} */
+ @Override
public final boolean isWrapperFor(Class<?> iface) throws SQLException
{
return iface.isInstance(delegate) || (delegate instanceof Wrapper && delegate.isWrapperFor(iface));
@@ -449,24 +474,19 @@ public abstract class ProxyConnection implements Connection
private static Connection getClosedConnection()
{
- InvocationHandler handler = new InvocationHandler() {
-
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
- {
- final String methodName = method.getName();
- if ("abort".equals(methodName)) {
- return Void.TYPE;
- }
- else if ("isValid".equals(methodName)) {
- return Boolean.FALSE;
- }
- else if ("toString".equals(methodName)) {
- return ClosedConnection.class.getCanonicalName();
- }
-
- throw new SQLException("Connection is closed");
+ InvocationHandler handler = (proxy, method, args) -> {
+ final String methodName = method.getName();
+ if ("abort".equals(methodName)) {
+ return Void.TYPE;
+ }
+ else if ("isValid".equals(methodName)) {
+ return Boolean.FALSE;
}
+ else if ("toString".equals(methodName)) {
+ return ClosedConnection.class.getCanonicalName();
+ }
+
+ throw new SQLException("Connection is closed");
};
return (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), new Class[] { Connection.class }, handler);
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyFactory.java b/src/main/java/com/zaxxer/hikari/pool/ProxyFactory.java
index 026debb..4a074da 100644
--- a/src/main/java/com/zaxxer/hikari/pool/ProxyFactory.java
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyFactory.java
@@ -30,6 +30,7 @@ import com.zaxxer.hikari.util.FastList;
*
* @author Brett Wooldridge
*/
+@SuppressWarnings("unused")
public final class ProxyFactory
{
private ProxyFactory()
@@ -39,13 +40,13 @@ public final class ProxyFactory
/**
* Create a proxy for the specified {@link Connection} instance.
- * @param poolEntry
- * @param connection
- * @param openStatements
- * @param leakTask
- * @param now
- * @param isReadOnly
- * @param isAutoCommit
+ * @param poolEntry the PoolEntry holding pool state
+ * @param connection the raw database Connection
+ * @param openStatements a reusable list to track open Statement instances
+ * @param leakTask the ProxyLeakTask for this connection
+ * @param now the current timestamp
+ * @param isReadOnly the default readOnly state of the connection
+ * @param isAutoCommit the default autoCommit state of the connection
* @return a proxy that wraps the specified {@link Connection}
*/
static ProxyConnection getProxyConnection(final PoolEntry poolEntry, final Connection connection, final FastList<Statement> openStatements, final ProxyLeakTask leakTask, final long now, final boolean isReadOnly, final boolean isAutoCommit)
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyLeakTask.java b/src/main/java/com/zaxxer/hikari/pool/ProxyLeakTask.java
index 0fdc93e..f72615e 100644
--- a/src/main/java/com/zaxxer/hikari/pool/ProxyLeakTask.java
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyLeakTask.java
@@ -32,10 +32,8 @@ import org.slf4j.LoggerFactory;
class ProxyLeakTask implements Runnable
{
private static final Logger LOGGER = LoggerFactory.getLogger(ProxyLeakTask.class);
- private static final ProxyLeakTask NO_LEAK;
+ static final ProxyLeakTask NO_LEAK;
- private ScheduledExecutorService executorService;
- private long leakDetectionThreshold;
private ScheduledFuture<?> scheduledFuture;
private String connectionName;
private Exception exception;
@@ -45,35 +43,29 @@ class ProxyLeakTask implements Runnable
{
NO_LEAK = new ProxyLeakTask() {
@Override
+ void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold) {}
+
+ @Override
+ public void run() {}
+
+ @Override
public void cancel() {}
};
}
- ProxyLeakTask(final long leakDetectionThreshold, final ScheduledExecutorService executorService)
- {
- this.executorService = executorService;
- this.leakDetectionThreshold = leakDetectionThreshold;
- }
-
- private ProxyLeakTask(final ProxyLeakTask parent, final PoolEntry poolEntry)
+ ProxyLeakTask(final PoolEntry poolEntry)
{
this.exception = new Exception("Apparent connection leak detected");
this.connectionName = poolEntry.connection.toString();
- scheduledFuture = parent.executorService.schedule(this, parent.leakDetectionThreshold, TimeUnit.MILLISECONDS);
}
private ProxyLeakTask()
{
}
-
- ProxyLeakTask schedule(final PoolEntry bagEntry)
- {
- return (leakDetectionThreshold == 0) ? NO_LEAK : new ProxyLeakTask(this, bagEntry);
- }
- void updateLeakDetectionThreshold(final long leakDetectionThreshold)
+ void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold)
{
- this.leakDetectionThreshold = leakDetectionThreshold;
+ scheduledFuture = executorService.schedule(this, leakDetectionThreshold, TimeUnit.MILLISECONDS);
}
/** {@inheritDoc} */
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyLeakTaskFactory.java b/src/main/java/com/zaxxer/hikari/pool/ProxyLeakTaskFactory.java
new file mode 100644
index 0000000..fde6074
--- /dev/null
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyLeakTaskFactory.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2013, 2014 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.pool;
+
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * A factory for {@link ProxyLeakTask} Runnables that are scheduled in the future to report leaks.
+ *
+ * @author Brett Wooldridge
+ * @author Andreas Brenk
+ */
+class ProxyLeakTaskFactory
+{
+ private ScheduledExecutorService executorService;
+ private long leakDetectionThreshold;
+
+ ProxyLeakTaskFactory(final long leakDetectionThreshold, final ScheduledExecutorService executorService)
+ {
+ this.executorService = executorService;
+ this.leakDetectionThreshold = leakDetectionThreshold;
+ }
+
+ ProxyLeakTask schedule(final PoolEntry poolEntry)
+ {
+ return (leakDetectionThreshold == 0) ? ProxyLeakTask.NO_LEAK : scheduleNewTask(poolEntry);
+ }
+
+ void updateLeakDetectionThreshold(final long leakDetectionThreshold)
+ {
+ this.leakDetectionThreshold = leakDetectionThreshold;
+ }
+
+ private ProxyLeakTask scheduleNewTask(PoolEntry poolEntry) {
+ ProxyLeakTask task = new ProxyLeakTask(poolEntry);
+ task.schedule(executorService, leakDetectionThreshold);
+
+ return task;
+ }
+}
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyPreparedStatement.java b/src/main/java/com/zaxxer/hikari/pool/ProxyPreparedStatement.java
index e2d96c9..68e47af 100644
--- a/src/main/java/com/zaxxer/hikari/pool/ProxyPreparedStatement.java
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyPreparedStatement.java
@@ -27,7 +27,7 @@ import java.sql.SQLException;
*/
public abstract class ProxyPreparedStatement extends ProxyStatement implements PreparedStatement
{
- protected ProxyPreparedStatement(ProxyConnection connection, PreparedStatement statement)
+ ProxyPreparedStatement(ProxyConnection connection, PreparedStatement statement)
{
super(connection, statement);
}
@@ -50,7 +50,7 @@ public abstract class ProxyPreparedStatement extends ProxyStatement implements P
{
connection.markCommitStateDirty();
ResultSet resultSet = ((PreparedStatement) delegate).executeQuery();
- return ProxyFactory.getProxyResultSet(connection, this, resultSet);
+ return ProxyFactory.getProxyResultSet(connection, this, resultSet);
}
/** {@inheritDoc} */
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyResultSet.java b/src/main/java/com/zaxxer/hikari/pool/ProxyResultSet.java
index 1933979..e2c4950 100644
--- a/src/main/java/com/zaxxer/hikari/pool/ProxyResultSet.java
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyResultSet.java
@@ -19,7 +19,6 @@ package com.zaxxer.hikari.pool;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
-import java.sql.Wrapper;
/**
* This is the proxy class for java.sql.ResultSet.
@@ -30,7 +29,7 @@ public abstract class ProxyResultSet implements ResultSet
{
protected final ProxyConnection connection;
protected final ProxyStatement statement;
- protected final ResultSet delegate;
+ final ResultSet delegate;
protected ProxyResultSet(ProxyConnection connection, ProxyStatement statement, ResultSet resultSet)
{
@@ -39,6 +38,7 @@ public abstract class ProxyResultSet implements ResultSet
this.delegate = resultSet;
}
+ @SuppressWarnings("unused")
final SQLException checkException(SQLException e)
{
return connection.checkException(e);
@@ -48,10 +48,7 @@ public abstract class ProxyResultSet implements ResultSet
@Override
public String toString()
{
- return new StringBuilder(64)
- .append(this.getClass().getSimpleName()).append('@').append(System.identityHashCode(this))
- .append(" wrapping ")
- .append(delegate).toString();
+ return this.getClass().getSimpleName() + '@' + System.identityHashCode(this) + " wrapping " + delegate;
}
// **********************************************************************
@@ -97,7 +94,7 @@ public abstract class ProxyResultSet implements ResultSet
if (iface.isInstance(delegate)) {
return (T) delegate;
}
- else if (delegate instanceof Wrapper) {
+ else if (delegate != null) {
return delegate.unwrap(iface);
}
diff --git a/src/main/java/com/zaxxer/hikari/pool/ProxyStatement.java b/src/main/java/com/zaxxer/hikari/pool/ProxyStatement.java
index 1d92cd8..bb5ac69 100644
--- a/src/main/java/com/zaxxer/hikari/pool/ProxyStatement.java
+++ b/src/main/java/com/zaxxer/hikari/pool/ProxyStatement.java
@@ -20,7 +20,6 @@ import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
-import java.sql.Wrapper;
/**
* This is the proxy class for java.sql.Statement.
@@ -30,17 +29,18 @@ import java.sql.Wrapper;
public abstract class ProxyStatement implements Statement
{
protected final ProxyConnection connection;
- protected final Statement delegate;
+ final Statement delegate;
private boolean isClosed;
private ResultSet proxyResultSet;
- protected ProxyStatement(ProxyConnection connection, Statement statement)
+ ProxyStatement(ProxyConnection connection, Statement statement)
{
this.connection = connection;
this.delegate = statement;
}
+ @SuppressWarnings("unused")
final SQLException checkException(SQLException e)
{
return connection.checkException(e);
@@ -51,10 +51,7 @@ public abstract class ProxyStatement implements Statement
public final String toString()
{
final String delegateToString = delegate.toString();
- return new StringBuilder(64 + delegateToString.length())
- .append(this.getClass().getSimpleName()).append('@').append(System.identityHashCode(this))
- .append(" wrapping ")
- .append(delegateToString).toString();
+ return this.getClass().getSimpleName() + '@' + System.identityHashCode(this) + " wrapping " + delegateToString;
}
// **********************************************************************
@@ -65,11 +62,14 @@ public abstract class ProxyStatement implements Statement
@Override
public final void close() throws SQLException
{
- if (isClosed) {
- return;
+ synchronized (this) {
+ if (isClosed) {
+ return;
+ }
+
+ isClosed = true;
}
- isClosed = true;
connection.untrackStatement(delegate);
try {
@@ -231,7 +231,7 @@ public abstract class ProxyStatement implements Statement
if (iface.isInstance(delegate)) {
return (T) delegate;
}
- else if (delegate instanceof Wrapper) {
+ else if (delegate != null) {
return delegate.unwrap(iface);
}
diff --git a/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java b/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java
index ac0ccb1..9822563 100755
--- a/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java
+++ b/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java
@@ -15,20 +15,23 @@
*/
package com.zaxxer.hikari.util;
+import static java.lang.Thread.yield;
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.concurrent.locks.LockSupport.parkNanos;
+
import static com.zaxxer.hikari.util.ClockSource.currentTime;
import static com.zaxxer.hikari.util.ClockSource.elapsedNanos;
import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_IN_USE;
import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_NOT_IN_USE;
import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_REMOVED;
import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_RESERVED;
-import static java.lang.Thread.yield;
-import static java.util.concurrent.TimeUnit.NANOSECONDS;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@@ -73,7 +76,7 @@ public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseab
private final SynchronousQueue<T> handoffQueue;
- public static interface IConcurrentBagEntry
+ public interface IConcurrentBagEntry
{
int STATE_NOT_IN_USE = 0;
int STATE_IN_USE = 1;
@@ -85,9 +88,9 @@ public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseab
int getState();
}
- public static interface IBagStateListener
+ public interface IBagStateListener
{
- Future<Boolean> addBagItem(int waiting);
+ void addBagItem(int waiting);
}
/**
@@ -147,7 +150,7 @@ public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseab
}
listener.addBagItem(waiting);
-
+
timeout = timeUnit.toNanos(timeout);
do {
final long start = currentTime();
@@ -179,11 +182,16 @@ public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseab
{
bagEntry.setState(STATE_NOT_IN_USE);
- while (waiters.get() > 0) {
- if (handoffQueue.offer(bagEntry)) {
+ for (int i = 0; waiters.get() > 0; i++) {
+ if (bagEntry.getState() != STATE_NOT_IN_USE || handoffQueue.offer(bagEntry)) {
return;
}
- yield();
+ else if ((i & 0xff) == 0xff) {
+ parkNanos(MICROSECONDS.toNanos(10));
+ }
+ else {
+ yield();
+ }
}
final List<Object> threadLocalList = threadList.get();
@@ -254,7 +262,9 @@ public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseab
*/
public List<T> values(final int state)
{
- return sharedList.stream().filter(e -> e.getState() == state).collect(Collectors.toList());
+ final List<T> list = sharedList.stream().filter(e -> e.getState() == state).collect(Collectors.toList());
+ Collections.reverse(list);
+ return list;
}
/**
diff --git a/src/main/java/com/zaxxer/hikari/util/DriverDataSource.java b/src/main/java/com/zaxxer/hikari/util/DriverDataSource.java
index 9aa9b6b..b40decf 100644
--- a/src/main/java/com/zaxxer/hikari/util/DriverDataSource.java
+++ b/src/main/java/com/zaxxer/hikari/util/DriverDataSource.java
@@ -66,12 +66,29 @@ public final class DriverDataSource implements DataSource
if (driver == null) {
LOGGER.warn("Registered driver with driverClassName={} was not found, trying direct instantiation.", driverClassName);
+ Class<?> driverClass = null;
try {
- Class<?> driverClass = this.getClass().getClassLoader().loadClass(driverClassName);
- driver = (Driver) driverClass.newInstance();
+ driverClass = this.getClass().getClassLoader().loadClass(driverClassName);
+ LOGGER.debug("Driver class found in the HikariConfig class classloader {}", this.getClass().getClassLoader());
+ } catch (ClassNotFoundException e) {
+ ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader();
+ if (threadContextClassLoader != null && threadContextClassLoader != this.getClass().getClassLoader()) {
+ try {
+ driverClass = threadContextClassLoader.loadClass(driverClassName);
+ LOGGER.debug("Driver class found in Thread context class loader {}", threadContextClassLoader);
+ } catch (ClassNotFoundException e1) {
+ LOGGER.warn("Failed to load class of driverClassName {} in either of HikariConfig class classloader {} or Thread context classloader {}", driverClassName, this.getClass().getClassLoader(), threadContextClassLoader);
+ }
+ } else {
+ LOGGER.warn("Failed to load class of driverClassName {} in HikariConfig class classloader {}", driverClassName, this.getClass().getClassLoader());
+ }
}
- catch (Exception e) {
- LOGGER.warn("Failed to create instance of driver class {}, trying jdbcUrl resolution", driverClassName, e);
+ if (driverClass != null) {
+ try {
+ driver = (Driver) driverClass.newInstance();
+ } catch (Exception e) {
+ LOGGER.warn("Failed to create instance of driver class {}, trying jdbcUrl resolution", driverClassName, e);
+ }
}
}
}
diff --git a/src/main/java/com/zaxxer/hikari/util/QueuedSequenceSynchronizer.java b/src/main/java/com/zaxxer/hikari/util/QueuedSequenceSynchronizer.java
deleted file mode 100644
index eac7ec7..0000000
--- a/src/main/java/com/zaxxer/hikari/util/QueuedSequenceSynchronizer.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2015 Brett Wooldridge
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.zaxxer.hikari.util;
-
-import java.util.concurrent.atomic.LongAdder;
-import java.util.concurrent.locks.AbstractQueuedLongSynchronizer;
-
-/**
- * A specialized wait/notify class useful for resource tracking through the
- * use of a monotonically-increasing long sequence.
- * <p>
- * When a shared resource becomes available the {@link #signal()} method should
- * be called unconditionally.
- * <p>
- * A thread wishing to acquire a shared resource should: <br>
- * <ul>
- * <li>Obtain the current sequence from the {@link #currentSequence()} method </li>
- * <li>Call {@link #waitUntilSequenceExceeded(long, long)} with that sequence. </li>
- * <li>Upon receiving a <code>true</code> result from {@link #waitUntilSequenceExceeded(long, long)},
- * the current sequence should again be obtained from the {@link #currentSequence()} method,
- * and an attempt to acquire the resource should be made. </li>
- * <li>If the shared resource cannot be acquired, the thread should again call
- * {@link #waitUntilSequenceExceeded(long, long)} with the previously obtained sequence. </li>
- * <li>If <code>false</code> is received from {@link #waitUntilSequenceExceeded(long, long)}
- * then a timeout has occurred. </li>
- * </ul>
- * <p>
- * When running on Java 8 and above, this class leverages the fact that when {@link LongAdder}
- * is monotonically increasing, and only {@link LongAdder#increment()} and {@link LongAdder#sum()}
- * are used, it can be relied on to be Sequentially Consistent.
- *
- * @see <a href="http://en.wikipedia.org/wiki/Sequential_consistency">Java Spec</a>
- * @author Brett Wooldridge
- */
-public final class QueuedSequenceSynchronizer
-{
- private final Sequence sequence;
- private final Synchronizer synchronizer;
-
- /**
- * Default constructor
- */
- public QueuedSequenceSynchronizer()
- {
- this.synchronizer = new Synchronizer();
- this.sequence = Sequence.Factory.create();
- }
-
- /**
- * Signal any waiting threads.
- */
- public void signal()
- {
- synchronizer.releaseShared(1);
- }
-
- /**
- * Get the current sequence.
- *
- * @return the current sequence
- */
- public long currentSequence()
- {
- return sequence.get();
- }
-
- /**
- * Block the current thread until the current sequence exceeds the specified threshold, or
- * until the specified timeout is reached.
- *
- * @param sequence the threshold the sequence must reach before this thread becomes unblocked
- * @param nanosTimeout a nanosecond timeout specifying the maximum time to wait
- * @return true if the threshold was reached, false if the wait timed out
- * @throws InterruptedException if the thread is interrupted while waiting
- */
- public boolean waitUntilSequenceExceeded(long sequence, long nanosTimeout) throws InterruptedException
- {
- return synchronizer.tryAcquireSharedNanos(sequence, nanosTimeout);
- }
-
- /**
- * Queries whether any threads are waiting to for the sequence to reach a particular threshold.
- *
- * @return true if there may be other threads waiting for a sequence threshold to be reached
- */
- public boolean hasQueuedThreads()
- {
- return synchronizer.hasQueuedThreads();
- }
-
- /**
- * Returns an estimate of the number of threads waiting for a sequence threshold to be reached. The
- * value is only an estimate because the number of threads may change dynamically while this method
- * traverses internal data structures. This method is designed for use in monitoring system state,
- * not for synchronization control.
- *
- * @return the estimated number of threads waiting for a sequence threshold to be reached
- */
- public int getQueueLength()
- {
- return synchronizer.getQueueLength();
- }
-
- private final class Synchronizer extends AbstractQueuedLongSynchronizer
- {
- private static final long serialVersionUID = 104753538004341218L;
-
- /** {@inheritDoc} */
- @Override
- protected long tryAcquireShared(final long seq)
- {
- return sequence.get() - (seq + 1);
- }
-
- /** {@inheritDoc} */
- @Override
- protected boolean tryReleaseShared(final long unused)
- {
- sequence.increment();
- return true;
- }
- }
-}
diff --git a/src/main/java/com/zaxxer/hikari/util/Sequence.java b/src/main/java/com/zaxxer/hikari/util/Sequence.java
deleted file mode 100644
index b7abd3c..0000000
--- a/src/main/java/com/zaxxer/hikari/util/Sequence.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2015 Brett Wooldridge
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.zaxxer.hikari.util;
-
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.LongAdder;
-
-/**
- * A monotonically increasing long sequence.
- *
- * @author brettw
- */
-@SuppressWarnings("serial")
-public interface Sequence
-{
- /**
- * Increments the current sequence by one.
- */
- void increment();
-
- /**
- * Get the current sequence.
- *
- * @return the current sequence.
- */
- long get();
-
- /**
- * Factory class used to create a platform-specific ClockSource.
- */
- final class Factory
- {
- public static Sequence create()
- {
- if (!Boolean.getBoolean("com.zaxxer.hikari.useAtomicLongSequence")) {
- return new Java8Sequence();
- }
- else {
- return new Java7Sequence();
- }
- }
- }
-
- final class Java7Sequence extends AtomicLong implements Sequence {
- @Override
- public void increment() {
- this.incrementAndGet();
- }
- }
-
- final class Java8Sequence extends LongAdder implements Sequence {
- @Override
- public long get() {
- return this.sum();
- }
- }
-}
diff --git a/src/test/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerTest.java b/src/test/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerTest.java
new file mode 100755
index 0000000..761ef12
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerTest.java
@@ -0,0 +1,39 @@
+package com.zaxxer.hikari.metrics.micrometer;
+
+import com.zaxxer.hikari.metrics.PoolStats;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MicrometerMetricsTrackerTest {
+
+ private MeterRegistry mockMeterRegistry = new SimpleMeterRegistry();
+
+ private MicrometerMetricsTracker testee;
+
+ @Before
+ public void setup(){
+ testee = new MicrometerMetricsTracker("mypool", new PoolStats(1000L) {
+ @Override
+ protected void update() {
+ // nothing
+ }
+ }, mockMeterRegistry);
+ }
+
+ @Test
+ public void close() throws Exception {
+ Assert.assertNotNull(mockMeterRegistry.find("Wait"));
+ Assert.assertNotNull(mockMeterRegistry.find("Usage"));
+ Assert.assertNotNull(mockMeterRegistry.find("ConnectionCreation"));
+ Assert.assertNotNull(mockMeterRegistry.find("ConnectionTimeoutRate"));
+ Assert.assertNotNull(mockMeterRegistry.find("TotalConnections"));
+ Assert.assertNotNull(mockMeterRegistry.find("IdleConnections"));
+ Assert.assertNotNull(mockMeterRegistry.find("ActiveConnections"));
+ Assert.assertNotNull(mockMeterRegistry.find("PendingConnections"));
+
+ testee.close();
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java b/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java
index 46b2e74..63746a3 100644
--- a/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java
+++ b/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java
@@ -60,10 +60,11 @@ public class HikariCPCollectorTest {
StubConnection.slowCreate = true;
try (HikariDataSource ds = new HikariDataSource(config)) {
- assertThat(getValue("hikaricp_active_connections", "HikariPool-1"), is(0.0));
- assertThat(getValue("hikaricp_idle_connections", "HikariPool-1"), is(0.0));
- assertThat(getValue("hikaricp_pending_threads", "HikariPool-1"), is(0.0));
- assertThat(getValue("hikaricp_connections", "HikariPool-1"), is(0.0));
+ String poolName = ds.getHikariConfigMXBean().getPoolName();
+ assertThat(getValue("hikaricp_active_connections", poolName), is(0.0));
+ assertThat(getValue("hikaricp_idle_connections", poolName), is(0.0));
+ assertThat(getValue("hikaricp_pending_threads", poolName), is(0.0));
+ assertThat(getValue("hikaricp_connections", poolName), is(0.0));
}
finally {
StubConnection.slowCreate = false;
@@ -105,7 +106,7 @@ public class HikariCPCollectorTest {
try (Connection connection1 = ds.getConnection()) {
// close immediately
}
-
+
assertThat(getValue("hikaricp_active_connections", "connectionClosed"), is(0.0));
assertThat(getValue("hikaricp_idle_connections", "connectionClosed"), is(1.0));
assertThat(getValue("hikaricp_pending_threads", "connectionClosed"), is(0.0));
diff --git a/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java b/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java
index a7b0b03..079cfb5 100644
--- a/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java
+++ b/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java
@@ -33,6 +33,7 @@ import com.zaxxer.hikari.HikariDataSource;
import io.prometheus.client.CollectorRegistry;
public class PrometheusMetricsTrackerTest {
+
@Test
public void recordConnectionTimeout() throws Exception {
HikariConfig config = newHikariConfig();
@@ -40,13 +41,13 @@ public class PrometheusMetricsTrackerTest {
config.setJdbcUrl("jdbc:h2:mem:");
config.setMaximumPoolSize(2);
config.setConnectionTimeout(250);
-
+
String[] labelNames = {"pool"};
String[] labelValues = {config.getPoolName()};
try (HikariDataSource hikariDataSource = new HikariDataSource(config)) {
try (Connection connection1 = hikariDataSource.getConnection();
- Connection connection2 = hikariDataSource.getConnection()) {
+ Connection connection2 = hikariDataSource.getConnection()) {
try (Connection connection3 = hikariDataSource.getConnection()) {
}
catch (SQLTransientConnectionException ignored) {
@@ -54,13 +55,13 @@ public class PrometheusMetricsTrackerTest {
}
assertThat(CollectorRegistry.defaultRegistry.getSampleValue(
- "hikaricp_connection_timeout_count",
+ "hikaricp_connection_timeout_total",
labelNames,
labelValues), is(1.0));
assertThat(CollectorRegistry.defaultRegistry.getSampleValue(
"hikaricp_connection_acquired_nanos_count",
labelNames,
- labelValues), is(equalTo(2.0)));
+ labelValues), is(equalTo(3.0)));
assertTrue(CollectorRegistry.defaultRegistry.getSampleValue(
"hikaricp_connection_acquired_nanos_sum",
labelNames,
@@ -75,4 +76,39 @@ public class PrometheusMetricsTrackerTest {
labelValues) > 0.0);
}
}
+
+ @Test
+ public void testMultiplePoolName() throws Exception {
+ String[] labelNames = {"pool"};
+
+ HikariConfig config = newHikariConfig();
+ config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
+ config.setPoolName("first");
+ config.setJdbcUrl("jdbc:h2:mem:");
+ config.setMaximumPoolSize(2);
+ config.setConnectionTimeout(250);
+ String[] labelValues1 = {config.getPoolName()};
+
+ try (HikariDataSource ignored = new HikariDataSource(config)) {
+ assertThat(CollectorRegistry.defaultRegistry.getSampleValue(
+ "hikaricp_connection_timeout_total",
+ labelNames,
+ labelValues1), is(0.0));
+
+ HikariConfig config2 = newHikariConfig();
+ config2.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
+ config2.setPoolName("second");
+ config2.setJdbcUrl("jdbc:h2:mem:");
+ config2.setMaximumPoolSize(4);
+ config2.setConnectionTimeout(250);
+ String[] labelValues2 = {config2.getPoolName()};
+
+ try (HikariDataSource ignored2 = new HikariDataSource(config2)) {
+ assertThat(CollectorRegistry.defaultRegistry.getSampleValue(
+ "hikaricp_connection_timeout_total",
+ labelNames,
+ labelValues2), is(0.0));
+ }
+ }
+ }
}
diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java b/src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java
index 5c48f85..2092611 100644
--- a/src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java
+++ b/src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java
@@ -45,7 +45,7 @@ import java.util.Calendar;
*/
public class StubPreparedStatement extends StubStatement implements PreparedStatement
{
- public StubPreparedStatement(Connection connection)
+ StubPreparedStatement(Connection connection)
{
super(connection);
}
diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubStatement.java b/src/test/java/com/zaxxer/hikari/mocks/StubStatement.java
index 73fec69..99e7b13 100644
--- a/src/test/java/com/zaxxer/hikari/mocks/StubStatement.java
+++ b/src/test/java/com/zaxxer/hikari/mocks/StubStatement.java
@@ -16,6 +16,8 @@
package com.zaxxer.hikari.mocks;
+import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
+
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -30,6 +32,8 @@ import java.sql.Statement;
public class StubStatement implements Statement
{
public static volatile boolean oldDriver;
+
+ private static volatile long simulatedQueryTime;
private boolean closed;
private Connection connection;
@@ -37,6 +41,10 @@ public class StubStatement implements Statement
this.connection = connection;
}
+ public static void setSimulatedQueryTime(long time) {
+ simulatedQueryTime = time;
+ }
+
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
@Override
@@ -168,6 +176,9 @@ public class StubStatement implements Statement
public boolean execute(String sql) throws SQLException
{
checkClosed();
+ if (simulatedQueryTime > 0) {
+ quietlySleep(simulatedQueryTime);
+ }
return false;
}
diff --git a/src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java b/src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java
index 761a802..a41b916 100644
--- a/src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java
+++ b/src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java
@@ -17,6 +17,10 @@ package com.zaxxer.hikari.osgi;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.model.InitializationError;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.PaxExam;
@@ -24,9 +28,9 @@ import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import javax.inject.Inject;
-
import java.io.File;
+import static com.zaxxer.hikari.pool.TestElf.isJava9;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.CoreOptions.*;
@@ -34,58 +38,76 @@ import static org.ops4j.pax.exam.CoreOptions.*;
/**
* @author lburgazzoli
*/
-@RunWith(PaxExam.class)
+@RunWith(OSGiBundleTest.ConditionalPaxExam.class)
public class OSGiBundleTest
{
- @Inject
- BundleContext context;
+ @Test
+ public void checkInject()
+ {
+ assertNotNull(context);
+ }
+
+ @Test
+ public void checkBundle()
+ {
+ Boolean bundleFound = false;
+ Boolean bundleActive = false;
- @Configuration
- public Option[] config()
- {
- return options(
- systemProperty("org.osgi.framework.storage.clean").value("true"),
- systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("WARN"),
- mavenBundle("org.slf4j","slf4j-api","1.7.5"),
- mavenBundle("org.slf4j","slf4j-simple","1.7.5").noStart(),
- mavenBundle("org.javassist", "javassist", "3.19.0-GA"),
- new File("target/classes").exists()
- ? bundle("reference:file:target/classes")
- : bundle("reference:file:../target/classes"),
- junitBundles(),
- cleanCaches()
- );
- }
+ Bundle[] bundles = context.getBundles();
+ for (Bundle bundle : bundles) {
+ if (bundle != null) {
+ if (bundle.getSymbolicName().equals("com.zaxxer.HikariCP")) {
+ bundleFound = true;
+ if (bundle.getState() == Bundle.ACTIVE) {
+ bundleActive = true;
+ }
+ }
+ }
+ }
- @Test
- public void checkInject()
- {
- assertNotNull(context);
- }
+ assertTrue(bundleFound);
+ assertTrue(bundleActive);
+ }
- @Test
- public void checkBundle()
- {
- Boolean bundleFound = false;
- Boolean bundleActive = false;
+ @Inject
+ BundleContext context;
- Bundle[] bundles = context.getBundles();
- for(Bundle bundle : bundles)
- {
- if(bundle != null)
- {
- if(bundle.getSymbolicName().equals("com.zaxxer.HikariCP"))
- {
- bundleFound = true;
- if(bundle.getState() == Bundle.ACTIVE)
- {
- bundleActive = true;
- }
- }
- }
- }
+ @Configuration
+ public Option[] config()
+ {
+ return options(
+ systemProperty("org.osgi.framework.storage.clean").value("true"),
+ systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("WARN"),
+ mavenBundle("org.slf4j", "slf4j-api", "1.7.5"),
+ mavenBundle("org.slf4j", "slf4j-simple", "1.7.5").noStart(),
+ new File("target/classes").exists()
+ ? bundle("reference:file:target/classes")
+ : bundle("reference:file:../target/classes"),
+ junitBundles(),
+ cleanCaches()
+ );
+ }
+
+ public static class ConditionalPaxExam extends PaxExam
+ {
+ public ConditionalPaxExam(Class<?> klass) throws InitializationError {
+ super(klass);
+ }
+
+ @Override
+ public void run(RunNotifier notifier) {
+ if (!isJava9()) {
+ super.run(notifier);
+ }
+ }
+
+ @Override
+ public void filter(Filter filter) throws NoTestsRemainException {
+ if (isJava9()) {
+ throw new NoTestsRemainException();
+ }
- assertTrue(bundleFound);
- assertTrue(bundleActive);
- }
+ super.filter(filter);
+ }
+ }
}
diff --git a/src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java b/src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java
index e66ba58..8585485 100755
--- a/src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java
+++ b/src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java
@@ -45,9 +45,9 @@ import com.zaxxer.hikari.mocks.StubDataSource;
*/
public class ConnectionPoolSizeVsThreadsTest {
- public static final Logger LOGGER = LoggerFactory.getLogger(ConnectionPoolSizeVsThreadsTest.class);
+ private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionPoolSizeVsThreadsTest.class);
- public static final int ITERATIONS = 50_000;
+ private static final int ITERATIONS = 50_000;
@Test
public void testPoolSizeAboutSameSizeAsThreadCount() throws Exception {
diff --git a/src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java b/src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java
index 31f8363..c2bd000 100755
--- a/src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java
+++ b/src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java
@@ -51,7 +51,7 @@ public class ConnectionRaceConditionTest
config.setMinimumIdle(0);
config.setMaximumPoolSize(10);
config.setInitializationFailTimeout(Long.MAX_VALUE);
- config.setConnectionTimeout(2500);
+ config.setConnectionTimeout(5000);
config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
setSlf4jLogLevel(ConcurrentBag.class, Level.INFO);
diff --git a/src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java b/src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java
index 1a7caa7..f62de43 100644
--- a/src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java
+++ b/src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java
@@ -59,7 +59,7 @@ public class HouseKeeperCleanupTest
config.setInitializationFailTimeout(Long.MAX_VALUE);
config.setConnectionTimeout(2500);
config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
- config.setScheduledExecutorService(executor);
+ config.setScheduledExecutor(executor);
HikariConfig config2 = newHikariConfig();
config.copyState(config2);
diff --git a/src/test/java/com/zaxxer/hikari/pool/MetricsTrackerTest.java b/src/test/java/com/zaxxer/hikari/pool/MetricsTrackerTest.java
new file mode 100644
index 0000000..494fd4d
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/MetricsTrackerTest.java
@@ -0,0 +1,86 @@
+package com.zaxxer.hikari.pool;
+
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.metrics.IMetricsTracker;
+import com.zaxxer.hikari.mocks.StubDataSource;
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.SQLTransientConnectionException;
+import java.util.concurrent.TimeUnit;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource;
+import static junit.framework.TestCase.fail;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author wvuong@chariotsolutions.com on 2/16/17.
+ */
+public class MetricsTrackerTest
+{
+
+ @Test(expected = SQLTransientConnectionException.class)
+ public void connectionTimeoutIsRecorded() throws Exception
+ {
+ int timeoutMillis = 1000;
+ int timeToCreateNewConnectionMillis = timeoutMillis * 2;
+
+ StubDataSource stubDataSource = new StubDataSource();
+ stubDataSource.setConnectionAcquistionTime(timeToCreateNewConnectionMillis);
+
+ StubMetricsTracker metricsTracker = new StubMetricsTracker();
+
+ try (HikariDataSource ds = newHikariDataSource()) {
+ ds.setMinimumIdle(0);
+ ds.setMaximumPoolSize(1);
+ ds.setConnectionTimeout(timeoutMillis);
+ ds.setDataSource(stubDataSource);
+ ds.setMetricsTrackerFactory((poolName, poolStats) -> metricsTracker);
+
+ try (Connection c = ds.getConnection()) {
+ fail("Connection shouldn't have been successfully created due to configured connection timeout");
+
+ } finally {
+ // assert that connection timeout was measured
+ assertThat(metricsTracker.connectionTimeoutRecorded, is(true));
+ // assert that measured time to acquire connection should be roughly equal or greater than the configured connection timeout time
+ assertTrue(metricsTracker.connectionAcquiredNanos >= TimeUnit.NANOSECONDS.convert(timeoutMillis, TimeUnit.MILLISECONDS));
+ }
+ }
+ }
+
+ private static class StubMetricsTracker implements IMetricsTracker
+ {
+
+ private Long connectionCreatedMillis;
+ private Long connectionAcquiredNanos;
+ private Long connectionBorrowedMillis;
+ private boolean connectionTimeoutRecorded;
+
+ @Override
+ public void recordConnectionCreatedMillis(long connectionCreatedMillis)
+ {
+ this.connectionCreatedMillis = connectionCreatedMillis;
+ }
+
+ @Override
+ public void recordConnectionAcquiredNanos(long elapsedAcquiredNanos)
+ {
+ this.connectionAcquiredNanos = elapsedAcquiredNanos;
+ }
+
+ @Override
+ public void recordConnectionUsageMillis(long elapsedBorrowedMillis)
+ {
+ this.connectionBorrowedMillis = elapsedBorrowedMillis;
+ }
+
+ @Override
+ public void recordConnectionTimeout()
+ {
+ this.connectionTimeoutRecorded = true;
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/MiscTest.java b/src/test/java/com/zaxxer/hikari/pool/MiscTest.java
index f9698ef..2791f3c 100644
--- a/src/test/java/com/zaxxer/hikari/pool/MiscTest.java
+++ b/src/test/java/com/zaxxer/hikari/pool/MiscTest.java
@@ -102,7 +102,7 @@ public class MiscTest
try (PrintStream ps = new PrintStream(baos, true)) {
setSlf4jTargetStream(Class.forName("com.zaxxer.hikari.pool.ProxyLeakTask"), ps);
setConfigUnitTest(true);
-
+
HikariConfig config = newHikariConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(4);
@@ -110,11 +110,11 @@ public class MiscTest
config.setMetricRegistry(null);
config.setLeakDetectionThreshold(TimeUnit.SECONDS.toMillis(1));
config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
+
try (HikariDataSource ds = new HikariDataSource(config)) {
setSlf4jLogLevel(HikariPool.class, Level.DEBUG);
getPool(ds).logPoolState();
-
+
try (Connection connection = ds.getConnection()) {
quietlySleep(SECONDS.toMillis(4));
connection.close();
@@ -128,6 +128,7 @@ public class MiscTest
finally
{
setConfigUnitTest(false);
+ setSlf4jLogLevel(HikariPool.class, Level.INFO);
}
}
}
diff --git a/src/test/java/com/zaxxer/hikari/pool/SaturatedPoolTest830.java b/src/test/java/com/zaxxer/hikari/pool/SaturatedPoolTest830.java
new file mode 100644
index 0000000..db8f970
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/SaturatedPoolTest830.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2017 Brett Wooldridge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.zaxxer.hikari.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.getConcurrentBag;
+import static com.zaxxer.hikari.pool.TestElf.getPool;
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.setSlf4jLogLevel;
+import static com.zaxxer.hikari.util.ClockSource.currentTime;
+import static com.zaxxer.hikari.util.ClockSource.elapsedMillis;
+import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
+import static java.lang.Math.round;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Arrays;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.logging.log4j.Level;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.mocks.StubConnection;
+import com.zaxxer.hikari.mocks.StubStatement;
+
+/**
+ * @author Brett Wooldridge
+ */
+public class SaturatedPoolTest830
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(SaturatedPoolTest830.class);
+ private static final int MAX_POOL_SIZE = 10;
+
+ @Test
+ public void saturatedPoolTest() throws Exception {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(5);
+ config.setMaximumPoolSize(MAX_POOL_SIZE);
+ config.setInitializationFailTimeout(Long.MAX_VALUE);
+ config.setConnectionTimeout(1000);
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ StubConnection.slowCreate = true;
+ StubStatement.setSimulatedQueryTime(1000);
+ setSlf4jLogLevel(HikariPool.class, Level.DEBUG);
+ System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "5000");
+
+ final long start = currentTime();
+
+ try (final HikariDataSource ds = new HikariDataSource(config)) {
+ LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
+ ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 50 /*core*/, 50 /*max*/, 2 /*keepalive*/, SECONDS, queue, new ThreadPoolExecutor.CallerRunsPolicy());
+ threadPool.allowCoreThreadTimeOut(true);
+
+ AtomicInteger windowIndex = new AtomicInteger();
+ boolean[] failureWindow = new boolean[100];
+ Arrays.fill(failureWindow, true);
+
+ // Initial saturation
+ for (int i = 0; i < 50; i++) {
+ threadPool.execute(() -> {
+ try (Connection conn = ds.getConnection();
+ Statement stmt = conn.createStatement()) {
+ stmt.execute("SELECT bogus FROM imaginary");
+ }
+ catch (SQLException e) {
+ LOGGER.info(e.getMessage());
+ }
+ });
+ }
+
+ long sleep = 80;
+outer: while (true) {
+ quietlySleep(sleep);
+
+ if (elapsedMillis(start) > SECONDS.toMillis(12) && sleep < 100) {
+ sleep = 100;
+ LOGGER.warn("Switching to 100ms sleep");
+ }
+ else if (elapsedMillis(start) > SECONDS.toMillis(6) && sleep < 90) {
+ sleep = 90;
+ LOGGER.warn("Switching to 90ms sleep");
+ }
+
+ threadPool.execute(() -> {
+ int ndx = windowIndex.incrementAndGet() % failureWindow.length;
+
+ try (Connection conn = ds.getConnection();
+ Statement stmt = conn.createStatement()) {
+ stmt.execute("SELECT bogus FROM imaginary");
+ failureWindow[ndx] = false;
+ }
+ catch (SQLException e) {
+ LOGGER.info(e.getMessage());
+ failureWindow[ndx] = true;
+ }
+ });
+
+ for (int i = 0; i < failureWindow.length; i++) {
+ if (failureWindow[i]) {
+ if (elapsedMillis(start) % (SECONDS.toMillis(1) - sleep) < sleep) {
+ LOGGER.info("Active threads {}, submissions per second {}, waiting threads {}",
+ threadPool.getActiveCount(),
+ SECONDS.toMillis(1) / sleep,
+ getPool(ds).getThreadsAwaitingConnection());
+ }
+ continue outer;
+ }
+ }
+
+ LOGGER.info("Timeouts have subsided.");
+ LOGGER.info("Active threads {}, submissions per second {}, waiting threads {}",
+ threadPool.getActiveCount(),
+ SECONDS.toMillis(1) / sleep,
+ getPool(ds).getThreadsAwaitingConnection());
+ break;
+ }
+
+ LOGGER.info("Waiting for completion of {} active tasks.", threadPool.getActiveCount());
+ while (getPool(ds).getActiveConnections() > 0) {
+ quietlySleep(50);
+ }
+
+ assertEquals("Rate not in balance at 10req/s", SECONDS.toMillis(1) / sleep, 10L);
+ }
+ finally {
+ StubStatement.setSimulatedQueryTime(0);
+ StubConnection.slowCreate = false;
+ System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs");
+ setSlf4jLogLevel(HikariPool.class, Level.INFO);
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java b/src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java
index 15f8974..2760543 100644
--- a/src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java
+++ b/src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java
@@ -251,9 +251,9 @@ public class TestConnectionTimeoutRetry
Connection connection5 = ds.getConnection();
Connection connection6 = ds.getConnection();
Connection connection7 = ds.getConnection()) {
-
+
sleep(1300);
-
+
assertSame("Total connections not as expected", 10, pool.getTotalConnections());
assertSame("Idle connections not as expected", 3, pool.getIdleConnections());
}
@@ -273,5 +273,6 @@ public class TestConnectionTimeoutRetry
public void after()
{
System.getProperties().remove("com.zaxxer.hikari.housekeeping.periodMs");
+ setSlf4jLogLevel(HikariPool.class, Level.INFO);
}
}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestElf.java b/src/test/java/com/zaxxer/hikari/pool/TestElf.java
index 6438b11..7ec2925 100644
--- a/src/test/java/com/zaxxer/hikari/pool/TestElf.java
+++ b/src/test/java/com/zaxxer/hikari/pool/TestElf.java
@@ -19,7 +19,6 @@ package com.zaxxer.hikari.pool;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.sql.Connection;
-import java.util.HashMap;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Appender;
@@ -32,6 +31,7 @@ import org.slf4j.LoggerFactory;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.util.ConcurrentBag;
/**
* Utility methods for testing.
@@ -44,6 +44,10 @@ public final class TestElf
// default constructor
}
+ public static boolean isJava9() {
+ return System.getProperty("java.version").startsWith("9");
+ }
+
public static HikariPool getPool(HikariDataSource ds)
{
try {
@@ -56,20 +60,19 @@ public final class TestElf
}
}
- @SuppressWarnings("unchecked")
- public static HashMap<Object, HikariPool> getMultiPool(HikariDataSource ds)
+ static ConcurrentBag<?> getConcurrentBag(HikariDataSource ds)
{
try {
- Field field = ds.getClass().getDeclaredField("multiPool");
+ Field field = HikariPool.class.getDeclaredField("connectionBag");
field.setAccessible(true);
- return (HashMap<Object, HikariPool>) field.get(ds);
+ return (ConcurrentBag<?>) field.get(getPool(ds));
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
- public static boolean getConnectionCommitDirtyState(Connection connection)
+ static boolean getConnectionCommitDirtyState(Connection connection)
{
try {
Field field = ProxyConnection.class.getDeclaredField("isCommitStateDirty");
@@ -81,7 +84,7 @@ public final class TestElf
}
}
- public static void setConfigUnitTest(boolean unitTest)
+ static void setConfigUnitTest(boolean unitTest)
{
try {
Field field = HikariConfig.class.getDeclaredField("unitTest");
@@ -93,7 +96,7 @@ public final class TestElf
}
}
- public static void setSlf4jTargetStream(Class<?> clazz, PrintStream stream)
+ static void setSlf4jTargetStream(Class<?> clazz, PrintStream stream)
{
try {
Log4jLogger log4Jlogger = (Log4jLogger) LoggerFactory.getLogger(clazz);
@@ -114,7 +117,7 @@ public final class TestElf
}
}
- public static void setSlf4jLogLevel(Class<?> clazz, Level logLevel)
+ static void setSlf4jLogLevel(Class<?> clazz, Level logLevel)
{
try {
Log4jLogger log4Jlogger = (Log4jLogger) LoggerFactory.getLogger(clazz);
@@ -144,7 +147,7 @@ public final class TestElf
return config;
}
- public static HikariDataSource newHikariDataSource()
+ static HikariDataSource newHikariDataSource()
{
final StackTraceElement callerStackTrace = Thread.currentThread().getStackTrace()[2];
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestJNDI.java b/src/test/java/com/zaxxer/hikari/pool/TestJNDI.java
index b5cd40c..6e041cb 100644
--- a/src/test/java/com/zaxxer/hikari/pool/TestJNDI.java
+++ b/src/test/java/com/zaxxer/hikari/pool/TestJNDI.java
@@ -15,17 +15,21 @@
*/
package com.zaxxer.hikari.pool;
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import javax.naming.Context;
+import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.RefAddr;
import javax.naming.Reference;
+import com.zaxxer.hikari.HikariConfig;
import org.junit.Test;
import org.osjava.sj.jndi.AbstractContext;
@@ -33,6 +37,8 @@ import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariJNDIFactory;
import com.zaxxer.hikari.mocks.StubDataSource;
+import java.sql.Connection;
+
public class TestJNDI
{
@Test
@@ -94,6 +100,27 @@ public class TestJNDI
}
}
+ @Test
+ public void testJndiLookup4() throws Exception
+ {
+ System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.osjava.sj.memory.MemoryContextFactory");
+ System.setProperty("org.osjava.sj.jndi.shared", "true");
+ InitialContext ic = new InitialContext();
+
+ StubDataSource ds = new StubDataSource();
+
+ Context subcontext = ic.createSubcontext("java:/comp/env/jdbc");
+ subcontext.bind("java:/comp/env/jdbc/myDS", ds);
+
+ HikariConfig config = newHikariConfig();
+ config.setDataSourceJNDI("java:/comp/env/jdbc/myDS");
+
+ try (HikariDataSource hds = new HikariDataSource(config);
+ Connection conn = hds.getConnection()) {
+ assertNotNull(conn);
+ }
+ }
+
private class BogusContext extends AbstractContext
{
@Override
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestMBean.java b/src/test/java/com/zaxxer/hikari/pool/TestMBean.java
index 55ba733..a77c960 100644
--- a/src/test/java/com/zaxxer/hikari/pool/TestMBean.java
+++ b/src/test/java/com/zaxxer/hikari/pool/TestMBean.java
@@ -15,30 +15,83 @@
*/
package com.zaxxer.hikari.pool;
-import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.HikariPoolMXBean;
+import org.junit.Test;
+import javax.management.JMX;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import java.lang.management.ManagementFactory;
+import java.sql.Connection;
import java.sql.SQLException;
+import java.util.concurrent.TimeUnit;
-import org.junit.Test;
-
-import com.zaxxer.hikari.HikariConfig;
-import com.zaxxer.hikari.HikariDataSource;
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static org.junit.Assert.assertEquals;
public class TestMBean
{
- @Test
- public void testMBeanRegistration() throws SQLException
- {
- HikariConfig config = newHikariConfig();
- config.setMinimumIdle(0);
- config.setMaximumPoolSize(1);
- config.setRegisterMbeans(true);
- config.setConnectionTimeout(2800);
- config.setConnectionTestQuery("VALUES 1");
- config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
- try (HikariDataSource ds = new HikariDataSource(config)) {
- // Close immediately
- }
- }
+ @Test
+ public void testMBeanRegistration() throws SQLException {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(1);
+ config.setRegisterMbeans(true);
+ config.setConnectionTimeout(2800);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ // Close immediately
+ }
+ }
+
+ @Test
+ public void testMBeanReporting() throws SQLException, InterruptedException, MalformedObjectNameException {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(3);
+ config.setMaximumPoolSize(5);
+ config.setRegisterMbeans(true);
+ config.setConnectionTimeout(2800);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "100");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+
+ ds.setIdleTimeout(3000);
+
+ TimeUnit.SECONDS.sleep(1);
+
+ MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
+ ObjectName poolName = new ObjectName("com.zaxxer.hikari:type=Pool (testMBeanReporting)");
+ HikariPoolMXBean hikariPoolMXBean = JMX.newMXBeanProxy(mBeanServer, poolName, HikariPoolMXBean.class);
+
+ assertEquals(0, hikariPoolMXBean.getActiveConnections());
+ assertEquals(3, hikariPoolMXBean.getIdleConnections());
+
+ try (Connection connection = ds.getConnection()) {
+ assertEquals(1, hikariPoolMXBean.getActiveConnections());
+
+ TimeUnit.SECONDS.sleep(1);
+
+ assertEquals(3, hikariPoolMXBean.getIdleConnections());
+ assertEquals(4, hikariPoolMXBean.getTotalConnections());
+ }
+
+ TimeUnit.SECONDS.sleep(2);
+
+ assertEquals(0, hikariPoolMXBean.getActiveConnections());
+ assertEquals(3, hikariPoolMXBean.getIdleConnections());
+ assertEquals(3, hikariPoolMXBean.getTotalConnections());
+
+ }
+ finally {
+ System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs");
+ }
+ }
}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestMetrics.java b/src/test/java/com/zaxxer/hikari/pool/TestMetrics.java
index fcba58e..775bb49 100644
--- a/src/test/java/com/zaxxer/hikari/pool/TestMetrics.java
+++ b/src/test/java/com/zaxxer/hikari/pool/TestMetrics.java
@@ -44,8 +44,6 @@ import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
import com.zaxxer.hikari.metrics.dropwizard.CodahaleMetricsTrackerFactory;
import com.zaxxer.hikari.util.UtilityElf;
-import shaded.org.codehaus.plexus.interpolation.os.Os;
-
/**
* Test HikariCP/CodaHale metrics integration.
*
@@ -85,7 +83,7 @@ public class TestMetrics
@Test
public void testMetricUsage() throws SQLException
{
- assumeFalse(Os.isFamily(Os.FAMILY_WINDOWS));
+ assumeFalse(System.getProperty("os.name").contains("Windows"));
MetricRegistry metricRegistry = new MetricRegistry();
HikariConfig config = newHikariConfig();
@@ -156,19 +154,19 @@ public class TestMetrics
try (HikariDataSource ds = newHikariDataSource()) {
ds.setMaximumPoolSize(1);
ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
+
MetricRegistry metricRegistry = new MetricRegistry();
HealthCheckRegistry healthRegistry = new HealthCheckRegistry();
-
+
try {
try (Connection connection = ds.getConnection()) {
// close immediately
}
-
+
// After the pool as started, we can only set them once...
ds.setMetricRegistry(metricRegistry);
ds.setHealthCheckRegistry(healthRegistry);
-
+
// and never again...
ds.setMetricRegistry(metricRegistry);
fail("Should not have been allowed to set registry after pool started");
@@ -192,19 +190,19 @@ public class TestMetrics
try (HikariDataSource ds = newHikariDataSource()) {
ds.setMaximumPoolSize(1);
ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
+
MetricRegistry metricRegistry = new MetricRegistry();
HealthCheckRegistry healthRegistry = new HealthCheckRegistry();
-
+
ds.setMetricRegistry(metricRegistry);
ds.setHealthCheckRegistry(healthRegistry);
-
+
// before the pool is started, we can set it any number of times...
ds.setMetricRegistry(metricRegistry);
ds.setHealthCheckRegistry(healthRegistry);
-
+
try (Connection connection = ds.getConnection()) {
-
+
// after the pool is started, we cannot set it any more
ds.setMetricRegistry(metricRegistry);
fail("Should not have been allowed to set registry after pool started");
@@ -221,15 +219,15 @@ public class TestMetrics
try (HikariDataSource ds = newHikariDataSource()) {
ds.setMaximumPoolSize(1);
ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
+
MetricRegistry metricRegistry = new MetricRegistry();
MetricsTrackerFactory metricsTrackerFactory = new CodahaleMetricsTrackerFactory(metricRegistry);
-
+
try (Connection connection = ds.getConnection()) {
-
+
// After the pool as started, we can only set them once...
ds.setMetricsTrackerFactory(metricsTrackerFactory);
-
+
// and never again...
ds.setMetricsTrackerFactory(metricsTrackerFactory);
fail("Should not have been allowed to set metricsTrackerFactory after pool started");
@@ -254,16 +252,16 @@ public class TestMetrics
try (HikariDataSource ds = newHikariDataSource()) {
ds.setMaximumPoolSize(1);
ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
+
MetricRegistry metricRegistry = new MetricRegistry();
-
+
// before the pool is started, we can set it any number of times using either setter
ds.setMetricRegistry(metricRegistry);
ds.setMetricRegistry(metricRegistry);
ds.setMetricRegistry(metricRegistry);
-
+
try (Connection connection = ds.getConnection()) {
-
+
// after the pool is started, we cannot set it any more
ds.setMetricRegistry(metricRegistry);
fail("Should not have been allowed to set registry after pool started");
@@ -280,17 +278,17 @@ public class TestMetrics
try (HikariDataSource ds = newHikariDataSource()) {
ds.setMaximumPoolSize(1);
ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
+
MetricRegistry metricRegistry = new MetricRegistry();
MetricsTrackerFactory metricsTrackerFactory = new CodahaleMetricsTrackerFactory(metricRegistry);
-
+
// before the pool is started, we can set it any number of times using either setter
ds.setMetricsTrackerFactory(metricsTrackerFactory);
ds.setMetricsTrackerFactory(metricsTrackerFactory);
ds.setMetricsTrackerFactory(metricsTrackerFactory);
-
+
try (Connection connection = ds.getConnection()) {
-
+
// after the pool is started, we cannot set it any more
ds.setMetricsTrackerFactory(metricsTrackerFactory);
fail("Should not have been allowed to set registry factory after pool started");
diff --git a/src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java b/src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java
index 06957b2..1c25a8f 100644
--- a/src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java
+++ b/src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java
@@ -16,9 +16,12 @@
package com.zaxxer.hikari.util;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
+import com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.DataInputStream;
import java.io.IOException;
@@ -26,15 +29,16 @@ import java.lang.ref.Reference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
import java.util.concurrent.CompletableFuture;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry;
+import static com.zaxxer.hikari.pool.TestElf.isJava9;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeTrue;
/**
* @author Brett Wooldridge
@@ -45,6 +49,8 @@ public class TomcatConcurrentBagLeakTest
@Test
public void testConcurrentBagForLeaks() throws Exception
{
+ assumeTrue(!isJava9());
+
ClassLoader cl = new FauxWebClassLoader();
Class<?> clazz = cl.loadClass(this.getClass().getName() + "$FauxWebContext");
Object fauxWebContext = clazz.newInstance();
@@ -60,6 +66,8 @@ public class TomcatConcurrentBagLeakTest
@Test
public void testConcurrentBagForLeaks2() throws Exception
{
+ assumeTrue(!isJava9());
+
ClassLoader cl = this.getClass().getClassLoader();
Class<?> clazz = cl.loadClass(this.getClass().getName() + "$FauxWebContext");
Object fauxWebContext = clazz.newInstance();
@@ -127,12 +135,15 @@ public class TomcatConcurrentBagLeakTest
}
}
+ @SuppressWarnings("unused")
public static class FauxWebContext
{
private static final Logger log = LoggerFactory.getLogger(FauxWebContext.class);
+ @SuppressWarnings("WeakerAccess")
public Exception failureException;
+ @SuppressWarnings("unused")
public void createConcurrentBag() throws InterruptedException
{
try (ConcurrentBag<PoolEntry> bag = new ConcurrentBag<>((x) -> CompletableFuture.completedFuture(Boolean.TRUE))) {
@@ -169,19 +180,19 @@ public class TomcatConcurrentBagLeakTest
Method expungeStaleEntriesMethod = tlmClass.getDeclaredMethod("expungeStaleEntries");
expungeStaleEntriesMethod.setAccessible(true);
- for (int i = 0; i < threads.length; i++) {
+ for (Thread thread : threads) {
Object threadLocalMap;
- if (threads[i] != null) {
+ if (thread != null) {
// Clear the first map
- threadLocalMap = threadLocalsField.get(threads[i]);
+ threadLocalMap = threadLocalsField.get(thread);
if (null != threadLocalMap) {
expungeStaleEntriesMethod.invoke(threadLocalMap);
checkThreadLocalMapForLeaks(threadLocalMap, tableField);
}
// Clear the second map
- threadLocalMap = inheritableThreadLocalsField.get(threads[i]);
+ threadLocalMap = inheritableThreadLocalsField.get(thread);
if (null != threadLocalMap) {
expungeStaleEntriesMethod.invoke(threadLocalMap);
checkThreadLocalMapForLeaks(threadLocalMap, tableField);
@@ -190,7 +201,7 @@ public class TomcatConcurrentBagLeakTest
}
}
catch (Throwable t) {
- log.warn("Failed to check for ThreadLocal references for web application [{}]", t);
+ log.warn("Failed to check for ThreadLocal references for web application [{}]", getContextName(), t);
failureException = new Exception();
}
}
@@ -212,8 +223,7 @@ public class TomcatConcurrentBagLeakTest
if (map != null) {
Object[] table = (Object[]) internalTableField.get(map);
if (table != null) {
- for (int j = 0; j < table.length; j++) {
- Object obj = table[j];
+ for (Object obj : table) {
if (obj != null) {
boolean keyLoadedByWebapp = false;
boolean valueLoadedByWebapp = false;
@@ -236,8 +246,7 @@ public class TomcatConcurrentBagLeakTest
args[1] = getPrettyClassName(key.getClass());
try {
args[2] = key.toString();
- }
- catch (Exception e) {
+ } catch (Exception e) {
log.warn("Unable to determine string representation of key of type [{}]", args[1], e);
args[2] = "Unknown";
}
@@ -246,8 +255,7 @@ public class TomcatConcurrentBagLeakTest
args[3] = getPrettyClassName(value.getClass());
try {
args[4] = value.toString();
- }
- catch (Exception e) {
+ } catch (Exception e) {
log.warn("webappClassLoader.checkThreadLocalsForLeaks.badValue {}", args[3], e);
args[4] = "Unknown";
}
@@ -255,21 +263,19 @@ public class TomcatConcurrentBagLeakTest
if (valueLoadedByWebapp) {
log.error("The web application [{}] created a ThreadLocal with key " +
- "(value [{}]) and a value of type [{}] (value [{}]) but failed to remove " +
- "it when the web application was stopped. Threads are going to be renewed " +
- "over time to try and avoid a probable memory leak.", args);
+ "(value [{}]) and a value of type [{}] (value [{}]) but failed to remove " +
+ "it when the web application was stopped. Threads are going to be renewed " +
+ "over time to try and avoid a probable memory leak.", args);
failureException = new Exception();
- }
- else if (value == null) {
+ } else if (value == null) {
log.debug("The web application [{}] created a ThreadLocal with key of type [{}] " +
- "(value [{}]). The ThreadLocal has been correctly set to null and the " +
- "key will be removed by GC.", args);
+ "(value [{}]). The ThreadLocal has been correctly set to null and the " +
+ "key will be removed by GC.", args);
failureException = new Exception();
- }
- else {
+ } else {
log.debug("The web application [{}] created a ThreadLocal with key of type [{}] " +
- "(value [{}]) and a value of type [{}] (value [{}]). Since keys are only " +
- "weakly held by the ThreadLocal Map this is not a memory leak.", args);
+ "(value [{}]) and a value of type [{}] (value [{}]). Since keys are only " +
+ "weakly held by the ThreadLocal Map this is not a memory leak.", args);
failureException = new Exception();
}
}
@@ -279,9 +285,45 @@ public class TomcatConcurrentBagLeakTest
}
}
- private boolean loadedByThisOrChild(Object key)
- {
- return key.getClass().getClassLoader() == this.getClass().getClassLoader();
+ /**
+ * @param o object to test, may be null
+ * @return <code>true</code> if o has been loaded by the current classloader
+ * or one of its descendants.
+ */
+ private boolean loadedByThisOrChild(Object o) {
+ if (o == null) {
+ return false;
+ }
+
+ Class<?> clazz;
+ if (o instanceof Class) {
+ clazz = (Class<?>) o;
+ } else {
+ clazz = o.getClass();
+ }
+
+ ClassLoader cl = clazz.getClassLoader();
+ while (cl != null) {
+ if (cl == this.getClass().getClassLoader()) {
+ return true;
+ }
+ cl = cl.getParent();
+ }
+
+ if (o instanceof Collection<?>) {
+ Iterator<?> iter = ((Collection<?>) o).iterator();
+ try {
+ while (iter.hasNext()) {
+ Object entry = iter.next();
+ if (loadedByThisOrChild(entry)) {
+ return true;
+ }
+ }
+ } catch (ConcurrentModificationException e) {
+ log.warn("Failed to check for ThreadLocal references for web application [{}]", getContextName(), e);
+ }
+ }
+ return false;
}
/*