Repository: brettwooldridge/HikariCP Branch: dev Commit: bba167f0a289 Files: 139 Total size: 819.2 KB Directory structure: gitextract_ly1ilzq_/ ├── .circleci/ │ └── config.yml ├── .editorconfig ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── .settings/ │ ├── org.eclipse.core.resources.prefs │ ├── org.eclipse.jdt.apt.core.prefs │ ├── org.eclipse.jdt.core.prefs │ ├── org.eclipse.m2e.core.prefs │ └── org.eclipse.pde.core.prefs ├── CHANGES ├── KEYS.txt ├── LICENSE ├── README.md ├── codecov.yml ├── documents/ │ ├── Wall-of-Fame.md │ └── Welcome-To-The-Jungle.md ├── install-jdk.sh ├── osx-toolchains.xml ├── pom.xml └── src/ ├── main/ │ └── java/ │ ├── com/ │ │ └── zaxxer/ │ │ └── hikari/ │ │ ├── HikariConfig.java │ │ ├── HikariConfigMXBean.java │ │ ├── HikariCredentialsProvider.java │ │ ├── HikariDataSource.java │ │ ├── HikariJNDIFactory.java │ │ ├── HikariPoolMXBean.java │ │ ├── SQLExceptionOverride.java │ │ ├── hibernate/ │ │ │ ├── HikariConfigurationUtil.java │ │ │ └── HikariConnectionProvider.java │ │ ├── metrics/ │ │ │ ├── IMetricsTracker.java │ │ │ ├── MetricsTracker.java │ │ │ ├── MetricsTrackerFactory.java │ │ │ ├── PoolStats.java │ │ │ ├── dropwizard/ │ │ │ │ ├── CodaHaleMetricsTracker.java │ │ │ │ ├── CodahaleHealthChecker.java │ │ │ │ ├── CodahaleMetricsTrackerFactory.java │ │ │ │ ├── Dropwizard5MetricsTracker.java │ │ │ │ ├── Dropwizard5MetricsTrackerFactory.java │ │ │ │ └── DropwizardCommon.java │ │ │ ├── micrometer/ │ │ │ │ ├── MicrometerMetricsTracker.java │ │ │ │ └── MicrometerMetricsTrackerFactory.java │ │ │ └── prometheus/ │ │ │ ├── HikariCPCollector.java │ │ │ ├── PrometheusHistogramMetricsTracker.java │ │ │ ├── PrometheusHistogramMetricsTrackerFactory.java │ │ │ ├── PrometheusMetricsTracker.java │ │ │ └── PrometheusMetricsTrackerFactory.java │ │ ├── pool/ │ │ │ ├── HikariPool.java │ │ │ ├── PoolBase.java │ │ │ ├── PoolEntry.java │ │ │ ├── ProxyCallableStatement.java │ │ │ ├── ProxyConnection.java │ │ │ ├── ProxyDatabaseMetaData.java │ │ │ ├── ProxyFactory.java │ │ │ ├── ProxyLeakTask.java │ │ │ ├── ProxyLeakTaskFactory.java │ │ │ ├── ProxyPreparedStatement.java │ │ │ ├── ProxyResultSet.java │ │ │ └── ProxyStatement.java │ │ └── util/ │ │ ├── ClockSource.java │ │ ├── ConcurrentBag.java │ │ ├── Credentials.java │ │ ├── DriverDataSource.java │ │ ├── FastList.java │ │ ├── IsolationLevel.java │ │ ├── JavassistProxyFactory.java │ │ ├── PropertyElf.java │ │ ├── SuspendResumeLock.java │ │ └── UtilityElf.java │ └── module-info.java └── test/ ├── java/ │ └── com/ │ └── zaxxer/ │ └── hikari/ │ ├── HikariConfigTest.java │ ├── datasource/ │ │ └── TestSealedConfig.java │ ├── db/ │ │ └── BasicPoolTest.java │ ├── metrics/ │ │ ├── dropwizard/ │ │ │ ├── CodaHaleMetricsTrackerTest.java │ │ │ └── Dropwizard5MetricsTrackerTest.java │ │ ├── micrometer/ │ │ │ └── MicrometerMetricsTrackerTest.java │ │ └── prometheus/ │ │ ├── HikariCPCollectorTest.java │ │ ├── PrometheusHistogramMetricsTrackerFactoryTest.java │ │ ├── PrometheusHistogramMetricsTrackerTest.java │ │ ├── PrometheusMetricsTrackerFactoryTest.java │ │ └── PrometheusMetricsTrackerTest.java │ ├── mocks/ │ │ ├── MockDataSource.java │ │ ├── StubBaseConnection.java │ │ ├── StubConnection.java │ │ ├── StubDataSource.java │ │ ├── StubDriver.java │ │ ├── StubPoolStats.java │ │ ├── StubPreparedStatement.java │ │ ├── StubResultSet.java │ │ ├── StubStatement.java │ │ └── TestObject.java │ ├── osgi/ │ │ └── OSGiBundleTest.java │ ├── pool/ │ │ ├── CodahaleMetricsTest.java │ │ ├── ConcurrentCloseConnectionTest.java │ │ ├── ConnectionPoolSizeVsThreadsTest.java │ │ ├── ConnectionRaceConditionTest.java │ │ ├── ConnectionStateTest.java │ │ ├── Dropwizard5MetricsTest.java │ │ ├── ExceptionTest.java │ │ ├── HouseKeeperCleanupTest.java │ │ ├── IsolationTest.java │ │ ├── JdbcDriverTest.java │ │ ├── MetricsTrackerTest.java │ │ ├── MiscTest.java │ │ ├── PostgresTest.java │ │ ├── RampUpDown.java │ │ ├── RequestBoundariesTest.java │ │ ├── ShutdownTest.java │ │ ├── StatementTest.java │ │ ├── TestConcurrentBag.java │ │ ├── TestConnectionCloseBlocking.java │ │ ├── TestConnectionTimeoutRetry.java │ │ ├── TestConnections.java │ │ ├── TestCredentials.java │ │ ├── TestElf.java │ │ ├── TestHibernate.java │ │ ├── TestIsRunning.java │ │ ├── TestJNDI.java │ │ ├── TestJavassistCodegen.java │ │ ├── TestMBean.java │ │ ├── TestMetrics.java │ │ ├── TestMetricsBase.java │ │ ├── TestPropertySetter.java │ │ ├── TestProxies.java │ │ ├── TestSaturatedPool830.java │ │ ├── TestStates.java │ │ ├── TestValidation.java │ │ └── UnwrapTest.java │ └── util/ │ ├── ClockSourceTest.java │ ├── DriverDataSourceTest.java │ ├── PropertyElfTest.java │ ├── TestFastList.java │ ├── TomcatConcurrentBagLeakTest.java │ └── UtilityElfTest.java └── resources/ ├── duration-config.properties ├── hibernate.properties ├── log4j2-test.xml ├── postgres_init_script.sql ├── propfile1.properties ├── propfile2.properties └── propfile3.properties ================================================ FILE CONTENTS ================================================ ================================================ FILE: .circleci/config.yml ================================================ jobs: build: # name of your job machine: true # executor type resource_class: brettwooldridge/ubuntu steps: # Commands run in a Linux virtual machine environment - checkout - run: mvn clean package ================================================ FILE: .editorconfig ================================================ # EditorConfig is awesome: http://EditorConfig.org # top-most EditorConfig file root = true [**] end_of_line = lf insert_final_newline = true charset = utf-8 indent_style = space trim_trailing_whitespace = true [**.{java,xml}] indent_size = 3 [**.{yaml,yml}] indent_size = 2 ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: pull_request: branches: - master push: branches: - master # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: build: runs-on: ubuntu-20.04 strategy: matrix: java: ['11'] steps: - uses: actions/checkout@v2 - name: Set up JDK uses: actions/setup-java@v1 with: java-version: ${{ matrix.java }} - name: print Java version run: java -version - uses: actions/cache@v2 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - name: Build run: mvn --no-transfer-progress -B clean package --file pom.xml ================================================ FILE: .gitignore ================================================ .classpath .project .metadata target/ dependency-reduced-pom.xml .DS_Store **/*.iml *.class *.jar *.war *.ear *.iml *.iws *.ipr .tm_* .idea/ .gradle/ out/ ================================================ FILE: .settings/org.eclipse.core.resources.prefs ================================================ eclipse.preferences.version=1 encoding//src/main/java=UTF-8 encoding//src/main/resources=UTF-8 encoding//src/test/java=UTF-8 encoding//src/test/resources=UTF-8 encoding//target/classes=UTF-8 encoding/=UTF-8 ================================================ FILE: .settings/org.eclipse.jdt.apt.core.prefs ================================================ eclipse.preferences.version=1 org.eclipse.jdt.apt.aptEnabled=false ================================================ FILE: .settings/org.eclipse.jdt.core.prefs ================================================ eclipse.preferences.version=1 org.eclipse.jdt.core.classpath.outputOverlappingAnotherSource=ignore org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve org.eclipse.jdt.core.compiler.compliance=11 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.enumIdentifier=error org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore org.eclipse.jdt.core.compiler.processAnnotations=disabled org.eclipse.jdt.core.compiler.release=enabled org.eclipse.jdt.core.compiler.source=11 ================================================ FILE: .settings/org.eclipse.m2e.core.prefs ================================================ activeProfiles= eclipse.preferences.version=1 resolveWorkspaceProjects=true version=1 ================================================ FILE: .settings/org.eclipse.pde.core.prefs ================================================ BUNDLE_ROOT_PATH=target/classes eclipse.preferences.version=1 ================================================ FILE: CHANGES ================================================ HikariCP Changes Changes in 7.0.2 * decrease thread yield frequency in ConcurrentBag.unreserve() Changes in 7.0.1 * merged #2346 fix regression with setSchema behavior * decrease thread yield frequency in ConcurrentBag.requite() Changes in 7.0.0 * merged #2340 NoSuchMethodException error that is thrown when setting a metric registry, fixes to UtilityElf reflection code to use the correct method signature. * fixed #1294 add support for HikariCredentialsProvider class * fixed #2265 bail out of the pool filling loop if the thread is interrupted Changes in 6.3.3 * backport #2340 NoSuchMethodException error that is thrown when setting a metric registry, fixes to UtilityElf reflection code to use the correct method signature. Changes in 6.3.2 * fixed #2342 restore module-info.class to jar file, which was lost in 6.3.1 * fixed #2256 add support for legacy override of getUsername()/getPassword() of HikariDataSource. See project page for documentation of system property com.zaxxer.hikari.legacy.supportUserPassDataSourceOverride. * fixed #2323 right or wrong (wrt driver behavior) return to previous Connection.get/setSchema behavior * fixed #2288 upgrade dependencies and fix build warnings Changes in 6.3.1 * fixed #2315 source jar contains also binary .class files and missing some .java files * fixed #2307 remove improper hardcoded timout, use validationTimeout * fixed #2305 keep properties key and values as is rather than forcing stringification. Also fixes #2286 and #2304 * upgraded various maven plugin dependencies to latest versions Changes in 6.3.0 * increase keepaliveTime variance from 10% to 20% * merged #2266 support duration values for configuration from properties, such as 10ms, 20s, 30m, 40h or 50d * merged #2284 align logs from HikariPool.logPoolState() * merged #2285 set default value for maxIdle in HikariConfig constructor (no functional change) * merged #2294 Allow setting String[] and int[] properties (useful for pgjdbc HA configuration) * merged #2300 catch java.lang.Error instead of java.lang.Exception under certain conditions Changes in 6.2.1 * change default keepaliveTime to 2 minutes * fix commons-compress dependency, make test scope Changes in 6.2.0 * merged #2238 handle SQLTimeoutException without eviction. Users looking to preserve previous behavior should provide an implementation of com.zaxxer.hikari.SQLExceptionOverride to the pool configuration. * added new enum value, Override.MUST_EVICT, available to implementations of com.zaxxer.hikari.SQLExceptionOverride * enhanced debug logging in circumstances where the pool falls to zero size and new connections to the database continue to fail. * update test dependencies that were flagged as having vulnerabilities Changes in 6.1.0 * fixed #1960 allow SQLExceptionOverride to adjudicate all exceptions for eviction * merged #1962 dropwizard 5 metrics are now supported via the setMetricRegistry() method in HikariConfig and in HikariDataSource * merged #2244 improve JavassistProxyFactory * merged #2243 fix inconsistency between isWrapperFor and unwrap * merged #1827 support loading properties file in unnamed resources module * merged #1842 don't clear isCommitStateDirty flag in setReadOnly * change default maxLifetime variance from 2.5% to 25% to further avoid mass connection die-off dips Changes in 6.0.0 * fixed #2152 duplicate connection in try with resources clause caused close() being called twice on each connection * merged #2226 consistent handling of errorCode and sqlState in timeout exception * merged #2199 eliminate network call if state get is called after set * merged #2189 add support to get and set db credentials in an atomic operation * merged #2149 make Savepoint rollbacks mark the connection dirty * merged #2157 close connections marked as evicted instead of returning them to the pool * merged #2147 skip Connection::setNetworkTimeout if PoolBase::shutdownNetworkTimeoutExecutor is called * merged #2126 added Support For beginRequest and endRequest * small improvements and cleanup from pull request #2166 * minor debug logging enhancements Changes in 5.1.0 * fixed #1907 re-added automatic.module.name that was lost along the way * fixed #1986 evict connection exceptions with (SQLState HY000) error code 1105. * merged #2059 add support for char[] in DataSource properties * merged #1979 and #1993 mask any property that looks like a password in URLs * add pool stats to connection acquisition timeout exception * merged #2076 don't case metric registry before it is checked for null * merged #1820 allow minimum login timeout to be set as system property * merged #1952 add more isolation levels * merged #1660 remove redundant error log for poll initialization exception Changes in 5.0.1 * Update log4j version to 2.17.1 to address Log4Shell vulnerability (although this is only used in tests, so it doesn't really impact users) Changes in 5.0.0 * rewrote connection elide/add code to fix an unconfirmed but occasionally reported race condition that results in the pool draining to 0 and not refilling. Changes in 4.0.3 * fixed #1735 added system property to permit override of lower limit of connectionTimeout and validation timeout Changes in 4.0.2 * fixed regression caused by #1605 affecting block-until-filled semantic Changes in 4.0.1 * fixed #1725 pom file change to be more gradle-friendly * fixed #1726 regression in micrometrics caused by lack of a strong reference to an object Changes in 4.0.0 * merged #1669 #1582 mark optional dependencies as 'require static' in module-info * merged #1700 remove micrometer metrics upon close * merged #1661 mark generated proxy classes final * merged #1681 allow alternate, more standard, JMX ObjectName, enabled by setting system property 'hikaricp.jmx.register2.0=true' * merged #1605 fixes Java 11 issue where setMaximumPoolSize needs to be called before setCorePoolSize in ThreadPoolExecutor * merged #1581 handle setting java 'short' property values from property files * merged #1699 add new configuration property 'keepaliveTime' * merged #1692 fix prometheus histogram metric tracker for multiple pools Changes in 3.4.5 * fixed 1578 build change to ensure that proxies are generated using Java 8, otherwise we end up with class references to Java 11 classes, which fail to load on Java 8. Changes in 3.4.4 * Build HikariCP as a JEP 238 Multi-Release jar for increased compatability with Java 11 and future modularized Java versions. Changes in 3.4.3 * fixed 1534 check resultSet.getStatement() for null in ProxyDatabaseMetaData. * add additional debug logging around connection add. * add ThreadLocal removal attempt when bag item is removed. Changes in 3.4.2 * fixed 1528 regression caused by pull request 1159 whereby fail-fast logic would exit upon all errors even if it was configured to continue trying. Changes in 3.4.1 * fix regression caused by 1337, which broke the ability to pass the isolation level by integer value (string) instead of isolation level name. Changes in 3.4.0 * merged 1265 alternative Prometheus metrics Histogram instead of locking Summary (which is purportedly quite lock-heavy. * merged 1337 support accepting TRANSACTION_SQL_SERVER_SNAPSHOT_ISOLATION_LEVEL string for isolation level. * merged 1331 user contribution: major refactor of metrics handling, hopefully without any breakage. * add proxy class for DatabaseMetaData to intercept DatabaseMetaData.getConnection() call which was leaking the raw database Connection object. Changes in 3.3.1 * fixed 1287 set core pool size before max pool size * fixed 1308 treat a SQLTimeoutException as an evictable offense * do not default maxPoolSize to minIdle when only minIdle is specified * do not log warning about idleTimeout unless minimumIdle is less than maxPoolSize (because otherwise it does not apply) Changes in 3.3.0 * Revert change where Connection.isClosed() was called outside of setNetworkTimeout() block, opening vulnerability to unacknowledged TCP traffic. * fixed 1186 limit number of items in the ConcurrentBag's ThreadLocal list to avoid OOM errors in some borrow/requite patterns. * Merged changed to log uncaught (Throwable) exceptions during connection acquisition. * fixed 1161 fix logging formatting anchor. * fixed 1181, 1182 allow passing a Prometheus CollectorRegistry rather than always using the default registry. * merged 1210 use orElseGet() to delay call until necessary with respect to the housekeeper thread factory. * fixed 1074 capability to instantiate an object based on the String class name, usefull when you want to set the MetricsTackerFactory from a property. * merged 1250 fix proxy classes generation reproducibility using a sorted LinkedHashSet * various clean-ups pointed out by sonarcloud.io * merged 1290 Class.newInstance() is deprecated as of Java 9. Usage should be replaced by calling Class.getDeclaredConstructor().newInstance(). * fixed #1305 ensure that ConcurrentBag.add() does not spin under high load, when waiting threads may never reach 0. * fixes #1287 when system property blockUntilFilled is set, use multiple threads to fill the pool. Changes in 3.2.0 * check connection closed condition before attempting to set network timeout to avoid spurios exceptions in the log. * updated validation failure message to include recommendation to check maxLifetime value. * fixed 1141 do not throw SQLException when calling isClosed() or close() on a already closed unwrapped Connection, as per JDBC specification. * fixed 1137 support changing user/password at runtime for DriverDataSource-wrapped driver connections. * fixed 1136 log loaded driver when using jdbcUrl. * pull 1135 extract sealed pool check into independent method. * fixed 1126 correct error in JavaDoc for HikariConfig.setInitializationFailTimeout(). * fixed 1114 removed unreachable code. Changes in 3.1.0 * Add get/setCatalog() to HikariConfigMXBean, allowing the catalog to be changed at runtime. The catalog should only be changed while the pool is suspended, and after evicting existing connections via HikariPoolMXBean.softEvictConnections(). Changes in 3.0.0 * Removed previously deprecated methods; HikariConfig.copyState() HikariConfig.getScheduledExecutorService() HikariConfig.setScheduledExecutorService() HikariConfig.isInitializationFailFast() HikariConfig.setInitializationFailFast() HikariConfig.isJdbc4ConnectionTest() HikariConfig.setJdbc4ConnectionTest() HikariDataSource.copyState() HikariDataSource.getScheduledExecutorService() HikariDataSource.setScheduledExecutorService() HikariDataSource.suspendPool() HikariDataSource.resumePool() HikariDataSource.shutdown() HikariDataSource.isInitializationFailFast() HikariDataSource.setInitializationFailFast() HikariDataSource.isJdbc4ConnectionTest() HikariDataSource.setJdbc4ConnectionTest() * pull 1110 add currently configured maxConnections and minConnections to pool metrics. * pull 1100 remove hard-coded percentiles for Micrometer metrics. * pull 1108 maintain a strong reference to PoolStats for Micrometer gauges to prevent premature garbage collection. * pull 1098 update to Micrometer 1.0.0. Changes in 2.7.8 * fixed 1095 fix breakage caused by sealed configuration with respect to special handling for the metricsRegistry and metricsTrackerFactory properties, which are allowed to be altered *once* after the pool has started (even after the configuration is sealed). * pull 1089 allowing anonymous subclasses of MetricRegistry. Changed checks for metrics libraries from a class name check to the assignableFrom() API. Changes in 2.7.7 * fixed issue whereby configuration through the HikariConfigMXBean could not be altered due to the sealed configuration change introduced in 2.7.5. Changes in 2.7.6 * issue 1064 fixed regression where HikariConfig.copyStateTo() propagated the "sealed" status of the source configuration to the target configuration -- preventing further changes. Changes in 2.7.5 * issue 1061/pull 1062 fixed compatibility issue with requery.io caused by HikariCP's proxied Statement class returning a driver-native ResultSet instance from getGeneratedKeys() instead of returning a HikariCP proxied ResultSet. * pull 1058 enable quantiles on Prometheus metrics. * pull 1055 fixed incorrect JavaDoc for HikariConfigMXBean.getMinimumIdle() method. * issue 1045/pull 1047 added Automatic-Module-Name to jar manifest to ensure that the Java 8 library plays well with the Java 9 module system. * introduced the concept of a "sealed" configuration. Once a pool is started, attempts to alter its configuration outside of the HikariConfigMXBean will result in an IllegalStateException. Changes in 2.7.4 * pull 1026 added support for SQL Server's specific isolation level (SNAPSHOT). * issue 926/pull 1022 HikariJNDIFactory should not throw a NamingException or else cascading to other object factories cannot occur. Changes in 2.7.3 * issue 1003 added PostgreSQL SQL State 0A000 to list of unrecoverable states calling for eviction. * pull 1002 updated micrometer support due to API changes in their release candidate. Changes in 2.7.2 * issue 983 fix logic that determines how many idle connections can be removed, without violating the minimumIdle contract. * pull 987 add thread name to leak detection messages. * issue 982 fix classloader order, try the ThreadContext classloader before other classloaders. * pull 977 log better messages when connection is evicted. * fallback to four digit random pool suffix when SecurityManager prevents writing to system properties for the purpose of JVM-wide unique pool identifiers. Changes in 2.7.1 * issue 968 Wrong label order in MicrometerMetricsTracker for the connection usage 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.4.13 * backport more efficient contention handling in ConcurrentBag.requite from 2.6.x branch. * issue 955 fix possible race condition when Statements are closed on different threads from which they were created. Changes in 2.7.0 * added support for micrometer metrics (currently Alpha-level support). * 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 add a new property initializationFailTimeout, and deprecate configuration property initializationFailFast. * issue 774 significantly improve spike load handling. * issues 518/769 add new metric for tracking how long physical connection acquisition is taking. DropWizard histogram name "ConnectionCreation", and Prometheus summary name "hikaricp_connection_creation_millis". * 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. * issue 781 more technically accurate wording of pool startup and shutdown log messages. Changes in 2.5.1 * issue 719 only reset lastConnectionFailure after a successful dataSource.getConnection() call. * issue 716 do not scan deeper than 10 nested SQLExceptions, it's typically a trap ... a chain that never terminates. * issue 714 fix possible issue with cross-thread visibility. Change pool entry state from AtomicInteger w/lazySet() to a volatile int with use of AtomicIntegerFieldUpdater. Changes in 2.5.0 * Release 2.5.0 marks the start of a Java 8 HikariCP artifact. The Java 7 artifact is now called "HikariCP-java7". * 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() call. * issue 716 do not scan deeper than 10 nested SQLExceptions, it's typically a trap ... a chain that never terminates. * issue 714 fix possible issue with cross-thread visibility. Change pool entry state from AtomicInteger w/lazySet() to a volatile int with use of AtomicIntegerFieldUpdater. Changes in 2.4.8 * Release 2.4.8 marks the start of a Java 7 HikariCP artifact, HikariCP-java7, representing support for Java 7 entering maintenance mode. * Added Connection.commit() call to the fail-fast initialization for databases that automatically start a new Connection in a transaction and throw an exception on close if it is not committed. * feature 694: report if a previously reported leaked connection is returned to the pool * issue 689: log a warning if default transaction isolation level cannot be detected. This can occur with pseudo-database drivers such as the one for JSonar * issue 674: fix regression caused by pull request #450 (overzealous optimisation) Changes in 2.4.7 * Miscellaneous stability improvements. * Removed Oracle SQL state 61000, added specific error code (2399) to evict connections when it is encountered. * issue 664: do not recycle PoolEntry objects that have closed their held connection. * issue 641, 643: reflection used method String.toUpperCase() without a Locale, which causes problems in some locales such as Turkish. * pull 632: added support for Prometheus metrics tracker. * issue 650: detect Amazon Redshift connection refused error codes. Changes in 2.4.6 * Added Oracle SQL error code 61000 (exceeded maximum connect time) to evict connections when it is encountered. * issue 621: fix NPE in shutdown if invoked during initialization. * issue 606, 610: housekeeper thread was running before all class members were initialized, causing an unrecoverable exception and disabling idle connection retirement (maximumLifetime still applied). Changes in 2.4.5 * issue 596: fix bug that occurs when minimumIdle is set to 0, but maximumPoolSize is not explicitly set. * issue 594: fix incompatibility with various libraries caused by storing a non-String object in System properties. * issue 593: improve logging when network timeout is not supported by the driver * issue 591: improve thread-safety of Statement proxies Changes in 2.4.4 * Generate unique sequential pool names, even across container classloaders to avoid JMX name collisions when registering pools. * Improve pool stability when running on computers using power-saving or sleep modes where wake-up previously caused pool to grow to maximum size. * Improve pool stability under severe thread-starvation conditions. Previous code could interpret prolonged starvation (exceeding one minute) as non-contiguous clock advancement, and reacted by soft-evicting connections (unnecessarily). * Added connection timeout rate to Dropwizard metrics (ConnectionTimeoutRate). * issue 563: Do not start the house-keeping thread until after pool initialisation has succeeded. * issue 559: Ensure the pool refill after house-keeping does not enqueue more add connection requests if there are already minimumIdle requests pending. * issue 555: include support for Java 8 interface 'default' methods during proxy generation. * issue 547: decreased allowable minimum connectionTimeout and validationTimeout to 250ms. * issue 495: implemented iterator() method on custom FastList to support Tomcat memory leak detection. Changes in 2.3.13 * issue 512: reduce the number of calls made to Connection.getAutoCommit(). Changes in 2.4.3 * Improve pool shutdown behavior. Stop active connection acquisition once the shutdown sequence has initiated. * Improved detection and reporting of ambiguous pool configuration, when both the connection URL and DataSource class names are specified. Changes in 2.4.2 * Improve accuracy of timeouts for getConnection() calls by accounting for possibly long delay aliveness tests. * Improve adherence to minimumIdle goal by closing idle connections starting from longest idle time to shortest. Additionally, stop when minimumIdle is reached even if connections exceeding idleTimeout remain (but are still within maxLifetime). * Introduce larger variance into maxLifetime to avoid mass connection closing and subsequent new connection creation load on the database. Connections now have a maximum lifetime between 97.5-100% of configured maxLifetime. In the case of the default 30 minute lifetime, this generates actual lifetimes with a maximum deviation of 45 seconds. Currently, no attempt is made to further avoid clustering that may occur due to randomness. * Ongoing com.zaxxer.hikari.metrics refactors. This is not considered public API until such time as we announce it. Caveat lector. * Performance improvements in the getConnection()/close() hot path. * issue 452: fixed race condition when creating an rapidly ramping connections in the pool. * issue 415: removed use of java.beans classes to allow use of HikariCP with the Zulu JRE compact3 profile. * issue 406: execute validation query during connection setup to make sure it is valid SQL. Changes in 2.3.12 * Fixed issue with proxy generation whereby the generated classes contain the major version number for Java 8, which makes them incompatible with the Java 7 runtime. Changes in 2.4.1 * issue 380: housekeeper was not being scheduled in the case of a user specified ScheduledExecutorService instance. * issue 340: rollback change that elides setting the readonly property if the user never explicitly configured it. See discussion in the Github issue tracker. Also fixes binary ABI breakage between 2.3.9 and 2.4.0. * issue 379: stop closing idle connections, to keep minimumIdle connections in pool * issue 375: fixed InvalidPathException in HikariConfig * issue 362: fixed NullPointerException in closing connection (closing statements) * issue 357: allow altering the username & password through JMX at runtime * issue 349: handle integer Transaction isolation level * Throw SQLTransientConnectionException instead of SQLTimeoutException * for validating connection, if network time out is set, do not set query timeout too * ResultSet.getStatement() should return StatementProxy Changes in 2.4.0 * Consolidated distribution into single JVM target (Java 7/8). Java 6 support has entered maintenance mode, bug fixes will continue on the 2.3.x branch. * Removed runtime dependency on Javassist by pre-generating proxy classes at build-time. * Significantly reduced overhead, and increased reliability, of ConcurrentBag. * Reduced garbage generation by 2-3x. * Add connection soft-eviction and replacement if backward system clock motion or significant forward jumps (greater than 1 minute) are detected. * Pool configuration properties and DataSource methods previously marked as @Deprecated have been removed. * Deprecated HikariDataSource.shutdown() in favor of close(). * Improve shutdown performance. * Allow user specified ScheduledThreadPoolExecutor for housekeeping timer. Useful in applications with dozens or hundreds of pools in the same JVM. * Reduce overhead and accuracy of Dropwizard gauges. Changes in 2.3.7 * Try harder at resolving the driver by various means when both driverClassName and jdbcUrl have been specified. * Allow a specifically set DataSource instance to override other settings such as jdbcUrl, dataSourceClassName, or driverClassName. * Fixed issue where, in the case of a driver-based configuration (jdbcUrl), we were not initialising the network timeout Executor. * Fixed race condition uncovered during load-testing in which the connections in the pool can spike to the maximum pool size when many connections reach their maxLifetime at the same time. Changes in 2.3.6 * Allow explicit definition of driverClassName to override DriverManager.getDriver(url) located driver. * Fixed a rare issue where a Connection that is held out of the pool, and never used by the holding thread, upon returning to the pool might be given to another thread without an aliveness test. Changes in 2.3.5 * Fixed regression caused by enhancement #279 that imposed a runtime dependency on Dropwizard metrics. Changes in 2.3.4 * Fixed class cast exception when setting the HealthCheckRegistry via JNDI lookup. * Allow Dropwizard MetricRegistry/HealthCheckRegistry to be set after pool startup -- one time only. * Make logger in BaseHikariPool non-static and use getClass() to log messages as the implementation class rather than as BaseHikariPool. * Removed deprecation from connectionInitSql, it will be allowed. * Made suspect/resume lock non-static (should be be shared across pools). * Improved unwrap() behavior in the Hibernate HikariConnectionProvider. * Improved leak detection log Changes in 2.3.3 * Fixed bad interaction with PostgeSQL JDBC driver whereby a SQLException thrown by PostgreSQL where the getNextException() call returns the original exception and causes an infinite loop in HikariCP (and eventual stack overflow). * Throw a typed Exception rather than a simple RuntimeException when pool initialization fails. * Allow Dropwizard Metrics and HealthChecks to be configured by a JNDI lookup. Changes in 2.3.2 * Add support for Dropwizard HealthChecks through the introduction of two initial health checks: ConnectivityCheck and Connection99Percent. See the Github project wiki for documentation. * Allow a lower maxLifetime setting of 30 seconds (compared to previous 120 second limit) * Improve the message displayed when a connection leak is detected. * Fixed a bug where Connection.setNetworkTimeout() was called on an already closed connection resulting in a warning log from the AS400 JDBC driver. Changes in 2.3.1 * Work around a bug in the MySQL Connector/J implementation of Connection.setNetworkTimeout() that results in non-deterministic asynchronous application of the timeout, resulting in an NPE from the MySQL driver when setNetworkTimeout() is followed immediately by close(). * Introduced a separate validationTimeout property, distict from connectionTimeout, to allow greater control for some deployments that desire a long (or infinite) connectionTimeout but expect the aliveness check to succeed for fail within a different (shorter) amount of time. Changes in 2.3.0 * Support pool suspend/resume to support certain failover scenarios. * Fix theoretical race in JDBC 4.0 detection support. * Improve shutdown() semantics to avoid exceptions as connections are forcefully aborted. * Unregister Codahale metrics at shutdown, if metrics are enabled. * Major internal project layout restructuring to allow shared use of common code between the Java 6/7 and Java 8 versions. * Fixed bug where two pools in the same VM (and ClassLoading domain), using drivers with differing JDBC support levels, would fail unless both pools were using connectionTestQuery. * Improved timeliness of maxLifetime evictions, while increasing performance of getConnection() slightly as a side-effect. * Fixed bug in HikariDataSource unwrap() semantics. * Allow a lower leakDetectionThreshold of 2 seconds. * Fixed bug when using the HikariJNDIFactory that required the presence of Codahale metrics. * Support initializationFailFast even when minimumIdle = 0 * Log internal pool inconsistencies rather than throwing exceptions that might disrupt internal executors. * Guard against poor or unreliable System.nanoTime() implementations. Changes in 2.2.5 * Fixes for Java 6 compatibility. * Implement full transaction state tracking. This allows HikariCP to bypass the automatic rollback when connections are returned to the pool if the transaction state is "clean". * Rename MBean closeIdleConnections() to softEvictConnections() and implement "evict on return" semantics. * Fixed bug in code that sets HikariConfig values from a Properties instance that prevented defaults from being read properly. * Fixed an obscure bug in connection creation with a driver that throws an exception when setTransactionIsolation() is called with the value returned by getTransactionIsolation(). We now bypass setTransactionIsolation() if the user has not configured an isolation level (using the default). * Fix a bug where DataSource.loginTimeout() was always being set to 1 second. * Fix bug where some drivers return 0 from Connection.getNetworkTimeout(), and yet throw SQLFeatureNotSupportedException when setNetworkTimeout() is called. This broke they way that HikariCP had implemented JDBC 4.1 support detection. Changes in 2.2.4 * Generate proxy classes into the same protection domain as the HikariCP loaded classes. This solves issues with signed jars. * Improve accuracy of pool statistics available to JMX and logged at debug level (at a slight performance cost). * Fixed issue where after a database down condition, and when minimumIdle is set to 0, when the database connectivity is restored the connections could ramp up to the maximum pool size. Eventually, idleTimeout and maxLifetime would restore normal pool conditions, but it was still undesirable behavior. * Improved connection timeout handling by using Connection.setNetworkTimeout() if available (JDBC 4.1). * driverClassName is no longer a required property when jdbcUrl is specified. Omitting this property only works for compliant drivers. * Add auto-detection of support for Statement.setQueryTimeout() used in the alive check. Fixes failures with test queries on the PostgreSQL driver when not using JDBC4 isValid() alive checks. * The pool now defaults to fail-fast initialization. If you need to start your application without/before the database, you will need to explicitly set initializationFailFast to false. * Dropwizard/Codahale metrics are now supported via the setMetricRegistry() method in HikariConfig and in HikariDataSource. * Fixed issue with pool initialization of MySQL after default value of initializationFailFast property was changed to false. * Further shadow runtime dependency on Codahale metrics from reflection performed by Spring and other IoC containers. * Fix issue where network timeout was not properly restored to its default value after modifying it for the duration of the addConnection() method. Changes in 2.1.0 * Significant internal refactor supporting creation of new proxy instances (throwaway) around Connections for each call to getConnection(). This can avoid issues where a thread continues to try to use a connection after it is closed [returned to the pool]. * Allow HikariConfig(String propertyFileName) to load properties file from classloader as a stream, with fall-back to the file-system. * Allow loading of properties file specified by -Dhikaricp.configurationFile system property when using the default HikariConfig() or HikariDataSource() constructors. * Fixed accounting issue with totalConnections when aborting connections during shutdown, causing a warning message to be logged. * Fixed regression in Java 8 codeline that would prevent minimumIdle from being set before maxPoolSize. * Fixed regression with Tomcat carping about ThreadLocal variables held after web application restart * Change to make HikariConfig.getTransactionIsolation()/setTransactionIsolation() follow proper bean semantics. * Fixed issue where connections created in the pool would skip the alive check the first time they were used. Changes in 2.0.1 * Split project into Java 6/7 and Java 8 components. * Fixed issue in JNDI object factory which would not allow JNDI-defined DataSource properties to pass-thru to the pool. * Fixed issue where under certain conditions getConnection() could timeout prematurely. * Fixed issue where user-defined pool name would be overridden by the automatically generated name. * Fixed NPE when one of either username and password is defined, and the other is null. * Fixed issue tracking the statements when there are mixed statement types (Statement, PreparedStatement, etc.) open on the connection and the number of unclosed statements exceeds 32. * Fixed issue where housekeeping threads would add idle connections even when minimumIdle was 0. * Fixed issue where Wrapper.isWrapperFor() and Wrapper.unwrap() calls did not recurse as per specification. * HikariDataSource now implements the Closable interface. * Integrated change to allow specifying a ThreadGroup for thread creation is certain restricted environments. Changes in 1.4.0 *) Fix bug that did not allow minIdle property to be set. Changes in 1.3.9 *) Added pool name to housekeeping thread name to make thread dumps more meaningful in containers with multiple pools. *) Improved shutdown semantics; make a concerted effort to close idle connections and abort or close active connections. *) Performance enhancements. Changes in 1.3.8 *) Fixed incorrect logic when using JDBC4 isValid() test for alive status of connection. Changes in 1.3.7 *) Added JNDI object factory (com.zaxxer.hikari.HikariJNDIFactory) for Tomcat and other containers that prefer JNDI-registered DataSource factories. *) Fix NPE that can occur when connections cannot be created and callers to getConnection() timeout. *) Various bug fixes and minor enhancements. Changes in 1.3.6 *) Include connection failure cause in calls to getConnection() that timeout (due to connection failure). Removed chatty logging. *) Java8 Compatibility fixes. *) Include pool name in logging messages. Thanks for the contribution @jaredstehler. Changes in 1.3.5 *) Fixed a regression in the Javassist code generation. *) Various bug fixes and minor enhancements. Changes in 1.3.4 *) Added new property isolateInternalQueries used to control whether internal pool queries such as connection alive tests are isolated in their own transaction. *) Added properties for DriverManager (driverClassName) and JDBC URL-based (jdbcUrl) configuration. 1999 called and wants its JDBC driver back. *) Added new username and password properties to allow default authentication for connections. *) Added support for the getConnection(username, password) method signature to HikariDataSource. *) Added new property readOnly to control the default read-only status of connections in the pool. *) Deprecated acquireIncrement property. *) Deprecated acquireRetries property. *) Deprecated acquireRetryDelay property. *) Deprecated minimumPoolSize property. *) Added new property minimumIdle used to control the minimum number of idle connections the pool should try to maintain on a running basis. *) Added evictConnection(Connection) method to HikariDataSource to allow special cases where users wish to forcibly eject a connection from the pool. To use used cautiously, read the JavaDoc before using. *) Various bug fixes and minor enhancements. Changes in 1.3.3 *) Removed shared state contention that was causing excessive CPU cache-line flushing. Nearly 4x improvement in Connection acquisition/release performance. *) Fixed issue with Tomcat carping about ThreadLocal variables held after web application restart. *) Fixed issue where the maximum configured connections could be overrun during large burst requests for connections. Changes in 1.3.2 *) Java 6 compatibility. *) HikariDataSource now extends HikariConfig, so pool properties can be set directly on a HikariDataSource without need to create a HikariConfig. The cost of doing so is a small runtime cost due to the fact that an "is initialized" check must be performed on every invocation of getConnection() due to lazy initialization of the pool. *) Added Sybase-specific disconnect error codes to SQLException snooping. *) Added HikariConfig.setCatalog() method to set DataSource catalog. *) Add DataSource.close() method that is synonymous with shutdown(). *) More performance improvements (never ending). *) Moved benchmarks to https://github.com/brettwooldridge/HikariCP-benchmark Changes in 1.3.0 *) Pool is now backed by a custom lock-less ConcurrentBag that provides superior performance to LinkedBlockingQueue and LinkedTransferQueue for usage patterns of connection pools. *) Fixed bugs reported against the 1.2.9 release. *) Added more detailed logging for broken connections and failures during new connection creation. Changes in 1.2.9 *) Added a fail-fast option for pool initialization. If enabled, a RuntimeException will be thrown if there are errors during pool initialization. *) Made the registration of the HikariCP MBeans optional. They now default to not being registered. Registering them causes a minor performance hit due to additional pool data collection in HikariDataSource.getConnection(). *) Added the SQLException message to the log entry when an exception occurs during physical connection acquisition. *) Implemented an orderly shutdown of the pool via the shutdown() method on HikariDataSource. *) Listened to "Adele - Live At The Royal Albert Hall" on endless loop. Changes in 1.2.8 *) Fixed a critical bug introduced in 1.2.7 occurring when the number of concurrently open statements exceeds sixteen. Changes in 1.2.7 *) Finally achieved performance parity between the generated delegates and the former method of instrumenting driver classes directly. *) Improved generated delegate code. Removed unnecessary casts, moved to a static proxy factory rather than a singleton (performance win). *) Improved performance of FastStatementList (primary source of speed-up to reach parity with former instrumentation code). *) Removed aliveness check on connection creation. *) Track connection isolation level and only reset if the state has become "dirty". Avoids unnecessary round trip to the DB during the aliveness check. *) Added interface IConnectionCustomizer and related HikariConfig property 'connectionCustomizerClassName' to allow users to specify a connection customization implementation. Changes in 1.2.6 *) Fixed regression that caused IndexOutOfBounds when multiple unclosed Statements existed at the time of Connection.close(). *) Fixed incorrect pom.xml dependency on Hibernate. Changes in 1.2.5 *) Instrumentation mode (agent) removed due to narrowing gap between delegation mode and instrumentation (and to simplify the code base). *) Added setDataSource() to HikariConfig to allow a DataSource instance to be explicitly wrapped by the pool. Only available when creating HikariConfig programmatically or constructing HikariConfig from a java.util.Properties instance. *) Fixed Hibernate threading issue (certain usage patterns) introduced in 1.2.2. *) Fixed issue observed with PostgreSQL whereby the query that tests the connection for "aliveness" also starts a transaction (when auto-commit is false), thereby causing a later failure when we tried to set the transaction isolation level. *) Fixed issue where idleTimeout could not be set to 0, thereby disabling it. Incorrect value validation caused 0 to be rejected as a valid value. Changes in 1.2.4 *) Fix another Hibernate-related issue whereby an NPE is encountered when a thread that was not the thread that obtained a Connection tries to interact with that Connection. Changes in 1.2.3 *) Fix internal (but suppressed) exception during rollback of connections returned to the pool with auto-commit turned off. *) Fix a reflection issue that causes Hibernate failures due to the CallableStatement interface being incorrectly injected into statement proxies that are PreparedStatement or Statement instances. Changes in 1.2.2 *) Perform a rollback() on connections returned to the pool with auto commit disabled. *) Add a constructor for HikariConfig that accepts a Properties object. *) Speed improvements for delegate mode. *) Fix a bug where connection timeouts could not be disabled. *) Permit setting the DataSource logWriter either on the HikariDataSource or via addDataSourceProperty() on the HikariConfig. *) Add transactionIsolation property to allow setting the default transaction isolation level for connections. Changes in 1.2.1 *) Clear SQL warnings before returning a connection to the user. *) Added asynchronous connection backfill strategy that triggers when the pool becomes empty as a result of dispatching a connection. *) Changed default acquireIncrement to 1, set minimum timeout of 100ms for acquiring a connection. Changes in 1.1.9 *) Added connectionInitSql property to allow setting connection properties when a new connection is created. *) Added setDataSourceProperties() setter to HikariConfig to allow easier configuration though Spring. ================================================ FILE: KEYS.txt ================================================ # GPG Release Key Fingerprints Brett Wooldridge F3A9 0E6B 10E8 09F8 51AB 4FC5 4CC0 8E7F 47C3 EC76 Leo Bayer 9579 802D C3E1 5DE9 C389 239F C0D4 8A11 9CE7 EE7B ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================ FILE: README.md ================================================

HikariCP It's Faster.Hi·ka·ri [hi·ka·'lē] (Origin: Japanese): light; ray.


[![][Build Status img]][Build Status] [![][Coverage Status img]][Coverage Status] [![][license img]][license] ![Maven Central Version](https://img.shields.io/maven-central/v/com.zaxxer/HikariCP?label=maven%20central&link=https%3A%2F%2Fcentral.sonatype.com%2Fartifact%2Fcom.zaxxer%2FHikariCP) [![][Javadocs img]][Javadocs] [![][Librapay img]][Librapay] Fast, simple, reliable. HikariCP is a "zero-overhead" production ready JDBC connection pool. At roughly 165Kb, the library is very light. Read about [how we do it here](https://github.com/brettwooldridge/HikariCP/wiki/Down-the-Rabbit-Hole).    **"Simplicity is prerequisite for reliability."**
         - *Dr. Edsger Dijkstra*
---------------------------------------------------- > [!IMPORTANT] > In order to avoid a rare condition where the pool goes to zero and does not recover it is necessary to configure *TCP keepalive*. Some JDBC drivers support this via properties, for example ``tcpKeepAlive=true`` on PostgreSQL, but in any case it can also be configured at the OS-level. See [Setting OS TCP Keepalive](https://github.com/brettwooldridge/HikariCP/wiki/Setting-Driver-or-OS-TCP-Keepalive) and/or [TCP keepalive for a better PostgreSQL experience](https://www.cybertec-postgresql.com/en/tcp-keepalive-for-a-better-postgresql-experience/#setting-tcp-keepalive-parameters-on-the-operating-system). ---------------------------------------------------- ### Index * [Artifacts](#artifacts) * [JMH Benchmarks](#checkered_flag-jmh-benchmarks) * [Analyses](#microscope-analyses) * [Spike Demand Pool Comparison](#spike-demand-pool-comparison) * [You're probably doing it wrong](#youre-probably-doing-it-wrong) * [WIX Engineering Analysis](#wix-engineering-analysis) * [Failure: Pools behaving badly](#failure-pools-behaving-badly) * [User Testimonials](#family-user-testimonials)
* [Configuration](#gear-configuration-knobs-baby)
* [Essentials](#essentials) * [Frequently used](#frequently-used) * [Infrequently used](#infrequently-used) * [Initialization](#rocket-initialization) ---------------------------------------------------- ### Artifacts _**Java 11+** maven artifact:_ ```xml com.zaxxer HikariCP 7.0.2 ``` _Java 8 maven artifact (*deprecated*):_ ```xml com.zaxxer HikariCP 4.0.3 ``` _Java 7 maven artifact (*deprecated*):_ ```xml com.zaxxer HikariCP-java7 2.4.13 ``` _Java 6 maven artifact (*deprecated*):_ ```xml com.zaxxer HikariCP-java6 2.3.13 ``` Or [download from here](http://search.maven.org/#search%7Cga%7C1%7Ccom.zaxxer.hikaricp). ---------------------------------------------------- ### :checkered_flag: JMH Benchmarks 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.6.0.png) * One *Connection Cycle* is defined as single ``DataSource.getConnection()``/``Connection.close()``. * One *Statement Cycle* is defined as single ``Connection.prepareStatement()``, ``Statement.execute()``, ``Statement.close()``. 1 Versions: HikariCP 2.6.0, commons-dbcp2 2.1.1, Tomcat 8.0.24, Vibur 16.1, c3p0 0.9.5.2, Java 8u111
2 Intel Core i7-3770 CPU @ 3.40GHz
3 Uncontended benchmark: 32 threads/32 connections, Contended benchmark: 32 threads, 16 connections
4 Apache Tomcat fails to complete the Statement benchmark when the Tomcat StatementFinalizer is used due to excessive garbage collection times
5 Apache DBCP fails to complete the Statement benchmark due to excessive garbage collection times
---------------------------------------------------- ### :microscope: Analyses #### Spike Demand Pool Comparison Analysis of HikariCP v2.6, in comparison to other pools, in relation to a unique "spike demand" load. 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).

#### You're [probably] doing it wrong 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 database connections do not need to be so numerous as they often are. In fact, too many connections 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).
#### WIX Engineering Analysis We'd like to thank the guys over at WIX for the unsolicited and deep write-up about HikariCP on their [engineering blog](https://www.wix.engineering/post/how-does-hikaricp-compare-to-other-connection-pools). Take a look if you have time.


#### Failure: Pools behaving badly Read our interesting ["Database down" pool challenge](https://github.com/brettwooldridge/HikariCP/wiki/Bad-Behavior:-Handling-Database-Down). ---------------------------------------------------- #### "Imitation Is The Sincerest Form Of Plagiarism" - anonymous 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.* ---------------------------------------------------- ### :family: User Testimonials [![](https://github.com/brettwooldridge/HikariCP/wiki/tweet3.png)](https://twitter.com/jkuipers)
[![](https://github.com/brettwooldridge/HikariCP/wiki/tweet1.png)](https://twitter.com/steve_objectify)
[![](https://github.com/brettwooldridge/HikariCP/wiki/tweet2.png)](https://twitter.com/brettemeyer)
[![](https://github.com/brettwooldridge/HikariCP/wiki/tweet4.png)](https://twitter.com/dgomesbr/status/527521925401419776) If you like this project, consider leaving a word for us on social media: [![](https://raw.github.com/wiki/brettwooldridge/HikariCP/twitter.png)](https://twitter.com/share?text=Interesting%20JDBC%20Connection%20Pool&hashtags=HikariCP&url=https%3A%2F%2Fgithub.com%2Fbrettwooldridge%2FHikariCP) [![](https://raw.github.com/wiki/brettwooldridge/HikariCP/facebook.png)](http://www.facebook.com/plugins/like.php?href=https%3A%2F%2Fgithub.com%2Fbrettwooldridge%2FHikariCP&width&layout=standard&action=recommend&show_faces=true&share=false&height=80) ------------------------------ ### :gear: Configuration (knobs, baby!) HikariCP comes with *sane* defaults that perform well in most deployments without additional tweaking. **Every property is optional, except for the "essentials" marked below.** 📎 *HikariCP uses milliseconds for all time values.* 🚨 HikariCP relies on accurate timers for both performance and reliability. It is *imperative* that your server is synchronized with a time-source such as an NTP server. *Especially* if your server is running within a virtual machine. Why? [Read more here](https://dba.stackexchange.com/a/171020). **Do not rely on hypervisor settings to "synchronize" the clock of the virtual machine. Configure time-source synchronization inside the virtual machine.** If you come asking for support on an issue that turns out to be caused by lack time synchronization, you will be taunted publicly on Twitter. #### Essentials 🔤``dataSourceClassName``
This is the name of the ``DataSource`` class provided by the JDBC driver. Consult the documentation for your specific JDBC driver to get this class name, or see the [table](https://github.com/brettwooldridge/HikariCP#popular-datasource-class-names) below. Note XA data sources are not supported. XA requires a real transaction manager like [bitronix](https://github.com/bitronix/btm). Note that you do not need this property if you are using ``jdbcUrl`` for "old-school" DriverManager-based JDBC driver configuration. *Default: none* *- or -* 🔤``jdbcUrl``
This property directs HikariCP to use "DriverManager-based" configuration. We feel that DataSource-based configuration (above) is superior for a variety of reasons (see below), but for many deployments there is little significant difference. **When using this property with "old" drivers, you may also need to set the ``driverClassName`` property, but try it first without.** Note that if this property is used, you may still use *DataSource* properties to configure your driver and is in fact recommended over driver parameters specified in the URL itself. *Default: none* *** 🔤``username``
This property sets the default authentication username used when obtaining *Connections* from the underlying driver. Note that for DataSources this works in a very deterministic fashion by calling ``DataSource.getConnection(*username*, password)`` on the underlying DataSource. However, for Driver-based configurations, every driver is different. In the case of Driver-based, HikariCP will use this ``username`` property to set a ``user`` property in the ``Properties`` passed to the driver's ``DriverManager.getConnection(jdbcUrl, props)`` call. If this is not what you need, skip this method entirely and call ``addDataSourceProperty("username", ...)``, for example. *Default: none* 🔤``password``
This property sets the default authentication password used when obtaining *Connections* from the underlying driver. Note that for DataSources this works in a very deterministic fashion by calling ``DataSource.getConnection(username, *password*)`` on the underlying DataSource. However, for Driver-based configurations, every driver is different. In the case of Driver-based, HikariCP will use this ``password`` property to set a ``password`` property in the ``Properties`` passed to the driver's ``DriverManager.getConnection(jdbcUrl, props)`` call. If this is not what you need, skip this method entirely and call ``addDataSourceProperty("pass", ...)``, for example. *Default: none* #### Frequently used ✅``autoCommit``
This property controls the default auto-commit behavior of connections returned from the pool. It is a boolean value. *Default: true* ⏳``connectionTimeout``
This property controls the maximum number of milliseconds that a client (that's you) will wait for a connection from the pool. If this time is exceeded without a connection becoming available, a SQLException will be thrown. Lowest acceptable connection timeout is 250 ms. *Default: 30000 (30 seconds)* ⏳``idleTimeout``
This property controls the maximum amount of time that a connection is allowed to sit idle in the pool. **This setting only applies when ``minimumIdle`` is defined to be less than ``maximumPoolSize``.** Idle connections will *not* be retired once the pool reaches ``minimumIdle`` connections. Whether a connection is retired as idle or not is subject to a maximum variation of +30 seconds, and average variation of +15 seconds. A connection will never be retired as idle *before* this timeout. A value of 0 means that idle connections are never removed from the pool. The minimum allowed value is 10000ms (10 seconds). *Default: 600000 (10 minutes)* ⏳``keepaliveTime``
This property controls how frequently HikariCP will attempt to keep a connection alive, in order to prevent it from being timed out by the database or network infrastructure. This value must be less than the `maxLifetime` value. A "keepalive" will only occur on an idle connection. When the time arrives for a "keepalive" against a given connection, that connection will be removed from the pool, "pinged", and then returned to the pool. The 'ping' is one of either: invocation of the JDBC4 `isValid()` method, or execution of the `connectionTestQuery`. Typically, the duration out-of-the-pool should be measured in single digit milliseconds or even sub-millisecond, and therefore should have little or no noticeable performance impact. The minimum allowed value is 30000ms (30 seconds), but a value in the range of minutes is most desirable. *Default: 120000 (2 minutes)* ⏳``maxLifetime``
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. On a connection-by-connection basis, minor negative attenuation is applied to avoid mass-extinction in the pool. **We strongly recommend setting this value, and it should be several seconds shorter 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. The minimum allowed value is 30000ms (30 seconds). *Default: 1800000 (30 minutes)* 🔤``connectionTestQuery``
**If your driver supports JDBC4 we strongly recommend not setting this property.** This is for "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.* *Default: none* 🔢``minimumIdle``
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 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* 🔢``maximumPoolSize``
This property controls the maximum size that the pool is allowed to reach, including both idle and in-use connections. Basically this value will determine the maximum number of actual connections to the database backend. 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. Please read [about pool sizing](https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing). *Default: 10* 📈``metricRegistry``
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/Dropwizard-Metrics) wiki page for details. *Default: none* 📈``healthCheckRegistry``
This property is only available via programmatic configuration or IoC container. This property allows you to specify an instance of a *Codahale/Dropwizard* ``HealthCheckRegistry`` to be used by the pool to report current health information. See the [Health Checks](https://github.com/brettwooldridge/HikariCP/wiki/Dropwizard-HealthChecks) wiki page for details. *Default: none* 🔤``poolName``
This property represents a user-defined name for the connection pool and appears mainly in logging and JMX management consoles to identify pools and pool configurations. *Default: auto-generated* #### Infrequently used ⏳``initializationFailTimeout``
This property controls whether the pool will "fail fast" if the pool cannot be seeded with 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* ❎``isolateInternalQueries``
This property determines whether HikariCP isolates internal pool queries, such as the connection alive test, in their own transaction. Since these are typically read-only queries, it is rarely necessary to encapsulate them in their own transaction. This property only applies if ``autoCommit`` is disabled. *Default: false* ❎``allowPoolSuspension``
This property controls whether the pool can be suspended and resumed through JMX. This is useful for certain failover automation scenarios. When the pool is suspended, calls to ``getConnection()`` will *not* timeout and will be held until the pool is resumed. *Default: false* ❎``readOnly``
This property controls whether *Connections* obtained from the pool are in read-only mode by default. Note some databases do not support the concept of read-only mode, while others provide query optimizations when the *Connection* is set to read-only. Whether you need this property or not will depend largely on your application and database. *Default: false* ❎``registerMbeans``
This property controls whether or not JMX Management Beans ("MBeans") are registered or not. *Default: false* 🔤``catalog``
This property sets the default *catalog* for databases that support the concept of catalogs. If this property is not specified, the default catalog defined by the JDBC driver is used. *Default: driver default* 🔤``connectionInitSql``
This property sets a SQL statement that will be executed after every new connection creation before adding it to the pool. If this SQL is not valid or throws an exception, it will be treated as a connection failure and the standard retry logic will be followed. *Default: none* 🔤``driverClassName``
HikariCP will attempt to resolve a driver through the DriverManager based solely on the ``jdbcUrl``, but for some older drivers the ``driverClassName`` must also be specified. Omit this property unless you get an obvious error message indicating that the driver was not found. *Default: none* 🔤``transactionIsolation``
This property controls the default transaction isolation level of connections returned from the pool. If this property is not specified, the default transaction isolation level defined by the JDBC driver is used. Only use this property if you have specific isolation requirements that are common for all queries. The value of this property is the constant name from the ``Connection`` class such as ``TRANSACTION_READ_COMMITTED``, ``TRANSACTION_REPEATABLE_READ``, etc. *Default: driver default* ⏳``validationTimeout``
This property controls the maximum amount of time that a connection will be tested for aliveness. This value must be less than the ``connectionTimeout``. Lowest acceptable validation timeout is 250 ms. *Default: 5000* ⏳``leakDetectionThreshold``
This property controls the amount of time that a connection can be out of the pool before a message is logged indicating a possible connection leak. A value of 0 means leak detection is disabled. Lowest acceptable value for enabling leak detection is 2000 (2 seconds). *Default: 0* ➡``dataSource``
This property is only available via programmatic configuration or IoC container. This property allows you to directly set the instance of the ``DataSource`` to be wrapped by the pool, rather than having HikariCP construct it via reflection. This can be useful in some dependency injection frameworks. When this property is specified, the ``dataSourceClassName`` property and all DataSource-specific properties will be ignored. *Default: none* 🔤``schema``
This property sets the default *schema* for databases that support the concept of schemas. If this property is not specified, the default schema defined by the JDBC driver is used. *Default: driver default* ➡``threadFactory``
This property is only available via programmatic configuration or IoC container. This property allows you to set the instance of the ``java.util.concurrent.ThreadFactory`` that will be used for creating all threads used by the pool. It is needed in some restricted execution environments where threads can only be created through a ``ThreadFactory`` provided by the application container. *Default: none* ➡``scheduledExecutor``
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* ➡``exceptionOverride``
This property is only available via programmatic configuration or IoC container. This property allows you to set an instance of a class, implementing the ``com.zaxxer.hikari.SQLExceptionOverride`` interface, that will be called before a connection is evicted from the pool due to specific exception conditions. Typically, when a ``SQLException`` is thrown, connections are evicted from the pool when specific *SQLStates* or *ErrorCodes* are present. The ``adjudicate()`` method will be called on the ``SQLExceptionOverride`` instance, which may return one of: ``Override.CONTINUE_EVICT``. ``Override.DO_NOT_EVICT`` or ``Override.MUST_EVICT``. Except in very specific cases ``Override.CONTINUE_EVICT`` should be returned, allowing the default evict/no-evict logic to execute. *Default: none* 🔤``exceptionOverrideClassName``
This property allows you to specify the name of a user-supplied class implementing the ``com.zaxxer.hikari.SQLExceptionOverride`` interface. An instance of the class will be instantiated by the pool to adjudicate connection evictions. See the above property ``exceptionOverride`` for a full description. *Default: none* ---------------------------------------------------- #### Missing Knobs HikariCP has plenty of "knobs" to turn as you can see above, but comparatively less than some other pools. This is a design philosophy. The HikariCP design aesthetic is Minimalism. In keeping with the *simple is better* or *less is more* design philosophy, some configuration axis are intentionally left out. #### Statement Cache Many connection pools, including Apache DBCP, Vibur, c3p0 and others offer ``PreparedStatement`` caching. HikariCP does not. Why? At the connection pool layer ``PreparedStatements`` can only be cached *per connection*. If your application has 250 commonly executed queries and a pool of 20 connections you are asking your database to hold on to 5000 query execution plans -- and similarly the pool must cache this many ``PreparedStatements`` and their related graph of objects. Most major database JDBC drivers already have a Statement cache that can be configured, including PostgreSQL, Oracle, Derby, MySQL, DB2, and many others. JDBC drivers are in a unique position to exploit database specific features, and nearly all of the caching implementations are capable of sharing execution plans *across connections*. This means that instead of 5000 statements in memory and associated execution plans, your 250 commonly executed queries result in exactly 250 execution plans in the database. Clever implementations do not even retain ``PreparedStatement`` objects in memory at the driver-level but instead merely attach new instances to existing plan IDs. Using a statement cache at the pooling layer is an [anti-pattern](https://en.wikipedia.org/wiki/Anti-pattern), and will negatively impact your application performance compared to driver-provided caches. #### Log Statement Text / Slow Query Logging Like Statement caching, most major database vendors support statement logging through properties of their own driver. This includes Oracle, MySQL, Derby, MSSQL, and others. Some even support slow query logging. For those few databases that do not support it, several options are available. We have received [a report that p6spy works well](https://github.com/brettwooldridge/HikariCP/issues/57#issuecomment-354647631), and also note the availability of [log4jdbc](https://github.com/arthurblake/log4jdbc) and [jdbcdslog-exp](https://code.google.com/p/jdbcdslog-exp/). #### Rapid Recovery Please read the [Rapid Recovery Guide](https://github.com/brettwooldridge/HikariCP/wiki/Rapid-Recovery) for details on how to configure your driver and system for proper recovery from database restart and network partition events. ---------------------------------------------------- ### :see_no_evil: Secret Properties HikariCP has several Java system properties that control various aspects of the pool. These properties are *unsupported* for user manipulation. It is possible though unlikely that they may not exist in the future. This means: do not open an issue of any kind if you have modified these properties. *Pretend you never heard anything about "secret properties".* | Property | Description | |:----------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ``com.zaxxer.hikari.blockUntilFilled`` | When this property is set ``true`` *and* ``initializationFailTimeout`` is greater than 1, the pool will block during start until completely filled. | | ``com.zaxxer.hikari.enableRequestBoundaries`` | When this property is set ``true``, HikariCP will bracket connection acquisition and return with calls to ``Connection.beginRequest()`` and ``Connection.endRequest()``. | | ``com.zaxxer.hikari.housekeeping.period`` | This property controls the frequency of the housekeeping thread, represented in milliseconds. Really, don't mess with this. | | ``com.zaxxer.hikari.legacy.supportUserPassDataSourceOverride`` | When this property is set ``true``, HikariCP will support the legacy behavior of overriding the ``getUsername()/getPassword()`` methods on *HikariDataSource*. Preferred method is overriding ``getCredentials()``. | | ``com.zaxxer.hikari.useWeakReferences`` | When this property is set ``true`` it will force HikariCP to use ``WeakReference`` objects in the ``ConcurrentBag`` internal collection ThreadLocals and prevent the use of our ``FastList`` class, all to avoid TomCat warnings during redeploy. | Either don't use these properties or take on full responsibility for the consequences. ### :rocket: Initialization You can use the ``HikariConfig`` class like so1: ```java HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/simpsons"); config.setUsername("bart"); config.setPassword("51mp50n"); config.addDataSourceProperty("cachePrepStmts", "true"); config.addDataSourceProperty("prepStmtCacheSize", "250"); config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); HikariDataSource ds = new HikariDataSource(config); ```  1 MySQL-specific example, DO NOT COPY VERBATIM. or directly instantiate a ``HikariDataSource`` like so: ```java HikariDataSource ds = new HikariDataSource(); ds.setJdbcUrl("jdbc:mysql://localhost:3306/simpsons"); ds.setUsername("bart"); ds.setPassword("51mp50n"); ... ``` or property file based: ```java // Examines both filesystem and classpath for .properties file HikariConfig config = new HikariConfig("/some/path/hikari.properties"); HikariDataSource ds = new HikariDataSource(config); ``` Example property file: ```ini dataSourceClassName=org.postgresql.ds.PGSimpleDataSource dataSource.user=test dataSource.password=test dataSource.databaseName=mydb dataSource.portNumber=5432 dataSource.serverName=localhost ``` or ``java.util.Properties`` based: ```java Properties props = new Properties(); props.setProperty("dataSourceClassName", "org.postgresql.ds.PGSimpleDataSource"); props.setProperty("dataSource.user", "test"); props.setProperty("dataSource.password", "test"); props.setProperty("dataSource.databaseName", "mydb"); props.put("dataSource.logWriter", new PrintWriter(System.out)); HikariConfig config = new HikariConfig(props); HikariDataSource ds = new HikariDataSource(config); ``` There is also a System property available, ``hikaricp.configurationFile``, that can be used to specify the 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 Performance 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*. ⚠ *Note: Spring Boot auto-configuration users, you need to use ``jdbcUrl``-based configuration.* ⚠ 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: | Database | Driver | *DataSource* class | |:---------------- |:------------ |:-------------------| | Apache Derby | Derby | org.apache.derby.jdbc.ClientDataSource | | Firebird | Jaybird | org.firebirdsql.ds.FBSimpleDataSource | | Google Spanner | Spanner | com.google.cloud.spanner.jdbc.JdbcDriver | | H2 | H2 | org.h2.jdbcx.JdbcDataSource | | HSQLDB | HSQLDB | org.hsqldb.jdbc.JDBCDataSource | | IBM DB2 | IBM JCC | com.ibm.db2.jcc.DB2SimpleDataSource | | IBM Informix | IBM Informix | com.informix.jdbcx.IfxDataSource | | MS SQL Server | Microsoft | com.microsoft.sqlserver.jdbc.SQLServerDataSource | | ~~MySQL~~ | Connector/J | ~~com.mysql.jdbc.jdbc2.optional.MysqlDataSource~~ | | MariaDB | MariaDB | org.mariadb.jdbc.MariaDbDataSource | | Oracle | Oracle | oracle.jdbc.pool.OracleDataSource | | OrientDB | OrientDB | com.orientechnologies.orient.jdbc.OrientDataSource | | PostgreSQL | pgjdbc-ng | com.impossibl.postgres.jdbc.PGDataSource | | PostgreSQL | PostgreSQL | org.postgresql.ds.PGSimpleDataSource | | SAP MaxDB | SAP | com.sap.dbtech.jdbc.DriverSapDB | | SQLite | xerial | org.sqlite.SQLiteDataSource | | SyBase | jConnect | com.sybase.jdbc4.jdbc.SybDataSource | ### Play Framework Plugin Note Play 2.4 now uses HikariCP by default. A new plugin has come up for the the Play framework; [play-hikaricp](http://edulify.github.io/play-hikaricp.edulify.com/). If you're using the excellent Play framework, your application deserves HikariCP. Thanks Edulify Team! ### Clojure Wrapper A new Clojure wrapper has been created by [tomekw](https://github.com/tomekw) and can be [found here](https://github.com/tomekw/hikari-cp). ### JRuby Wrapper A new JRuby wrapper has been created by [tomekw](https://github.com/tomekw) and can be [found here](https://github.com/tomekw/hucpa). ---------------------------------------------------- ### Wiki Don't forget the [Wiki](https://github.com/brettwooldridge/HikariCP/wiki) for additional information such as: * [FAQ](https://github.com/brettwooldridge/HikariCP/wiki/FAQ) * [Hibernate 4.x Configuration](https://github.com/brettwooldridge/HikariCP/wiki/Hibernate4) * [MySQL Configuration Tips](https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration) * etc. ---------------------------------------------------- ### Requirements ⇒ Java 11+ (Java 6/7/8 artifacts are in maintenance mode)
⇒ slf4j library
### Sponsors High-performance projects can never have too many tools! We would like to thank the following companies: Thanks to [ej-technologies](https://www.ej-technologies.com) for their excellent all-in-one profiler, [JProfiler](https://www.ej-technologies.com/products/jprofiler/overview.html). YourKit supports open source projects with its full-featured Java Profiler. Click the YourKit logo below to learn more.
[![](https://github.com/brettwooldridge/HikariCP/wiki/yklogo.png)](http://www.yourkit.com/java/profiler/index.jsp)
### Contributions Please perform changes and submit pull requests from the ``dev`` branch instead of ``master``. Please set your editor to use spaces instead of tabs, and adhere to the apparent style of the code you are editing. The ``dev`` branch is always more "current" than the ``master`` if you are looking to live life on the edge. [Build Status]:https://circleci.com/gh/brettwooldridge/HikariCP [Build Status img]:https://circleci.com/gh/brettwooldridge/HikariCP/tree/dev.svg?style=shield [Coverage Status]:https://codecov.io/gh/brettwooldridge/HikariCP [Coverage Status img]:https://codecov.io/gh/brettwooldridge/HikariCP/branch/dev/graph/badge.svg [license]:LICENSE [license img]:https://img.shields.io/badge/license-Apache%202-blue.svg [Javadocs]:http://javadoc.io/doc/com.zaxxer/HikariCP [Javadocs img]:http://javadoc.io/badge/com.zaxxer/HikariCP.svg [Librapay]:https://liberapay.com/brettwooldridge [Librapay img]:https://img.shields.io/liberapay/patrons/brettwooldridge.svg?logo=liberapay ================================================ FILE: codecov.yml ================================================ codecov: notify: require_ci_to_pass: yes coverage: precision: 2 round: down range: "50..80" status: project: yes patch: yes changes: no parsers: gcov: branch_detection: conditional: yes loop: yes method: no macro: no comment: layout: "reach, diff, flags, files, footer" behavior: default require_changes: no ================================================ FILE: documents/Wall-of-Fame.md ================================================ ![][spacer]![][spacer] ![][spacer]![][spacer] ![][spacer]![][spacer] ![][spacer]![][spacer] ![][spacer]![][spacer] ------------------------------------------------------------------------------------- * 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 ================================================ FILE: documents/Welcome-To-The-Jungle.md ================================================ Microbenchmarks are great at measuring performance "in the small"; for example, measuring the performance of individual methods. But good results do not necessarily translate into macro-scale performance. Real world access patterns and demand loads often run into deeper, systemic, architectural design issues that cannot be discerned at the micro level.

HikariCP has over 1 million users, so from time to time we are approached with challenges encountered "in the wild". Recently, one such challenge led to a deeper investigation: ***Spike Demand***. ### The Challenge The user has an environment where connection creation is expensive, on the order of 150ms; and yet queries typically execute in ~2ms. Long connection setup times can be the result of various factors, alone or in combination: DNS resolution times, encrypted connections with strong encryption (2048/4096 bit), external authentication, database server load, etc. *Generally speaking, for the best performance in response to spike demands, HikariCP recommends a fixed-size pool.* Unfortunately, the user's application is also in an environment where many other applications are connected to the same database, and therefore dynamically-sized pools are desirable -- where idle applications are allowed to give up some of their connections. The user is running the application with HikariCP configured as ``minimumIdle=5``. In this environment, the application has periods of quiet, as well as sudden spikes of requests, and periods of sustained activity. The combination of high connection setup times, a dynamically-sized pool requirement, and spike demands is just about the worst case scenario for a connection pool. The questions ultimately were these: > * If the pool is sitting idle with 5 connections, and is suddenly hit with 50 requests, what should happen? > * Given that a each new connection is going to take 150ms to establish, and given that each request can ultimately be satisfied in ~2ms, shouldn't even a single one of the idle connections be able to handle all the of the requests in ~100ms anyway? > * So, why is the pool size growing [so much]? We thought these were interesting questions, and HikariCP was indeed creating more connections than we expected... ### 3, 2, 1 ... Go! In order to explore these questions, we built a simulation and started measuring. The simulation harness [code is here](https://github.com/brettwooldridge/HikariCP-benchmark/blob/master/src/test/java/com/zaxxer/hikari/benchmark/SpikeLoadTest.java). The constraints are simple: * Connection establishment takes 150ms. * Query execution takes 2ms. * The maximum pool size is 50. * The minimum idle connections is 5. And the simulation is fairly simple: * Everything is quiet, and then ... Boom! ... 50 threads, at once, wanting a connection and to execute a query. * Take measurements every 250μs (microseconds). ### Results After running HikariCP through the simulation, tweaking the code (ultimately a one-line change), and satisfying ourselves that the behavior is as we would wish, we ran a few other pools through the simulation. The code was run as follows: ``` bash$ ./spiketest.sh 150 50 ``` Where ``150`` is the connection establishment time, ```` 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) raw data -------------------- [![](https://github.com/brettwooldridge/HikariCP/wiki/Spike-Hikari.png)](https://github.com/brettwooldridge/HikariCP/wiki/Spike-Hikari.png) #### Apache DBCP (v2.1.1) raw data -------------------- [![](https://github.com/brettwooldridge/HikariCP/wiki/Spike-DBCP2.png)](https://github.com/brettwooldridge/HikariCP/wiki/Spike-DBCP2.png) #### Apache Tomcat (v8.0.24) raw data -------------------- [![](https://github.com/brettwooldridge/HikariCP/wiki/Spike-Tomcat.png)](https://github.com/brettwooldridge/HikariCP/wiki/Spike-Tomcat.png) #### Vibur DBCP (v16.1) raw data -------------------- [![](https://github.com/brettwooldridge/HikariCP/wiki/Spike-Vibur.png)](https://github.com/brettwooldridge/HikariCP/wiki/Spike-Vibur.png) * 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. -------------------- ### Apache DBCP vs HikariCP :point_right: In case you missed the *time-scale* in the graphs above, here is a properly scaled comparable. Apache DBCP on *top*, HikariCP on the *bottom*. [![](https://github.com/brettwooldridge/HikariCP/wiki/Spike-Compare.png)](https://github.com/brettwooldridge/HikariCP/wiki/Spike-Compare.png) ### Commentary 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 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***.1
1 to the greatest extent possible. 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 does the prime directive apply in this case?" We'll answer with a question of our own: > If the thread is directed to create a new connection, and that connection takes 150ms to establish, what happens if one of the five in-use connections is returned to the pool? --------------------- 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?"*  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. ### 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. ================================================ FILE: install-jdk.sh ================================================ #!/usr/bin/env bash # # Install JDK for Linux and Mac OS # # This script determines the most recent early-access build number, # downloads the JDK archive to the user home directory and extracts # it there. # # Exported environment variables (when sourcing this script) # # JAVA_HOME is set to the extracted JDK directory # PATH is prepended with ${JAVA_HOME}/bin # # (C) 2020 Christian Stein # # https://github.com/sormuras/bach/blob/master/install-jdk.sh # set -o errexit #set -o nounset # https://github.com/travis-ci/travis-ci/issues/5434 #set -o xtrace function initialize() { readonly script_name="$(basename "${BASH_SOURCE[0]}")" readonly script_version='2020-03-17' dry=false silent=false verbose=false emit_java_home=false feature='ea' license='GPL' # Force GPLv2+CE os='?' url='?' workspace="${HOME}" target='?' cacerts=false } function usage() { cat << EOF Usage: ${script_name} [OPTION]... Download and extract latest-and-greatest JDK from https://jdk.java.net Version: ${script_version} Options: -h|--help Displays this help -d|--dry-run Activates dry-run mode -s|--silent Displays no output -e|--emit-java-home Print value of "JAVA_HOME" to stdout (ignores silent mode) -v|--verbose Displays verbose output -f|--feature 11|12|...|ea JDK feature release number, defaults to "ea" -o|--os linux-x64|osx-x64 Operating system identifier -u|--url "https://..." Use custom JDK archive (provided as .tar.gz file) -w|--workspace PATH Working directory defaults to \${HOME} [${HOME}] -t|--target PATH Target directory, defaults to first component of the tarball -c|--cacerts Link system CA certificates (currently only Debian/Ubuntu is supported) EOF } function script_exit() { if [[ $# -eq 1 ]]; then printf '%s\n' "$1" exit 0 fi if [[ $# -eq 2 && $2 =~ ^[0-9]+$ ]]; then printf '%b\n' "$1" exit "$2" fi script_exit 'Invalid arguments passed to script_exit()!' 2 } function say() { if [[ ${silent} != true ]]; then echo "$@" fi } function verbose() { if [[ ${verbose} == true ]]; then echo "$@" fi } function parse_options() { local option while [[ $# -gt 0 ]]; do option="$1" shift case ${option} in -h|-H|--help) usage exit 0 ;; -v|-V|--verbose) verbose=true ;; -s|-S|--silent) silent=true verbose "Silent mode activated" ;; -d|-D|--dry-run) dry=true verbose "Dry-run mode activated" ;; -e|-E|--emit-java-home) emit_java_home=true verbose "Emitting JAVA_HOME" ;; -f|-F|--feature) feature="$1" verbose "feature=${feature}" shift ;; -l|-L|--license) # license="$1" say "Ignoring license option: $1 -- using GPLv2+CE by default" verbose "license=${license}" shift ;; -o|-O|--os) os="$1" verbose "os=${os}" shift ;; -u|-U|--url) url="$1" verbose "url=${url}" shift ;; -w|-W|--workspace) workspace="$1" verbose "workspace=${workspace}" shift ;; -t|-T|--target) target="$1" verbose "target=${target}" shift ;; -c|-C|--cacerts) cacerts=true verbose "Linking system CA certificates" ;; *) script_exit "Invalid argument was provided: ${option}" 2 ;; esac done } function determine_latest_jdk() { local number local curl_result local url number=15 verbose "Determine latest JDK feature release number, starting with ${number}" while [[ ${number} != 99 ]] do url="https://jdk.java.net/${number}" curl_result=$(curl -o /dev/null --silent --head --write-out %{http_code} ${url}) if [[ ${curl_result} -ge 400 ]]; then break fi verbose " Found ${url} [${curl_result}]" latest_jdk=${number} number=$[$number +1] done verbose "Latest JDK feature release number is: ${latest_jdk}" } function perform_sanity_checks() { if [[ ${feature} == '?' ]] || [[ ${feature} == 'ea' ]]; then feature=${latest_jdk} fi if [[ ${feature} -lt 9 ]] || [[ ${feature} -gt ${latest_jdk} ]]; then script_exit "Expected feature release number in range of 9 to ${latest_jdk}, but got: ${feature}" 3 fi if [[ -d "$target" ]]; then script_exit "Target directory must not exist, but it does: $(du -hs '${target}')" 3 fi } function determine_url() { local JAVA_NET="https://jdk.java.net/${feature}" local DOWNLOAD='https://download.java.net/java' # An official GA build or an archived feature? Use predefined URL case "${feature}" in 9) url="${DOWNLOAD}/GA/jdk9/9.0.4/binaries/openjdk-9.0.4_${os}_bin.tar.gz"; return;; 10) url="${DOWNLOAD}/GA/jdk10/10.0.2/19aef61b38124481863b1413dce1855f/13/openjdk-10.0.2_${os}_bin.tar.gz"; return;; 11) url="${DOWNLOAD}/GA/jdk11/9/GPL/openjdk-11.0.2_${os}_bin.tar.gz"; return;; 12) url="${DOWNLOAD}/GA/jdk12.0.2/e482c34c86bd4bf8b56c0b35558996b9/10/GPL/openjdk-12.0.2_${os}_bin.tar.gz"; return;; 13) url="${DOWNLOAD}/GA/jdk13.0.2/d4173c853231432d94f001e99d882ca7/8/GPL/openjdk-13.0.2_${os}_bin.tar.gz"; return;; 14) url="${DOWNLOAD}/GA/jdk14/076bab302c7b4508975440c56f6cc26a/36/GPL/openjdk-14_${os}_bin.tar.gz"; return;; # 15) is still available from its EA/RC location determined below esac # EA or RC build? Grab URL from HTML source of jdk.java.net/${feature} local candidates=$(wget --quiet --output-document - ${JAVA_NET} | grep -Eo 'href[[:space:]]*=[[:space:]]*"[^\"]+"' | grep -Eo '(http|https)://[^"]+') url=$(echo "${candidates}" | grep -Eo "${DOWNLOAD}/.+/jdk${feature}/.*${license}/.*jdk-${feature}.+${os}_bin(.tar.gz|.zip)$" || true) if [[ -z ${url} ]]; then script_exit "Couldn't determine a download url for ${feature}-${license} on ${os}" 1 fi } function prepare_variables() { if [[ ${os} == '?' ]]; then if [[ "$OSTYPE" == "darwin"* ]]; then os='osx-x64' else os='linux-x64' fi fi if [[ ${url} == '?' ]]; then determine_latest_jdk perform_sanity_checks determine_url else feature='' license='' os='' fi status=$(curl -o /dev/null --silent --head --write-out %{http_code} ${url}) } function print_variables() { cat << EOF Variables: feature = ${feature} os = ${os} url = ${url} status = ${status} EOF } function download_and_extract_and_set_target() { local quiet='--quiet'; if [[ ${verbose} == true ]]; then quiet=''; fi local local="--directory-prefix ${workspace} --output-document=jdk.tar.gz" local remote='--timestamping --continue' local wget_options="${quiet} ${local} ${remote}" local tar_options="--file jdk.tar.gz" say "Downloading JDK from ${url}..." verbose "Using wget options: ${wget_options}" wget ${wget_options} ${url} if [[ ${os} == 'windows-x64' ]]; then script_exit "Extracting archives on Windows isn't supported, yet" 4 fi verbose "Using tar options: ${tar_options}" if [[ ${target} == '?' ]]; then tar --extract ${tar_options} -C "${workspace}" if [[ "$OSTYPE" != "darwin"* ]]; then target="${workspace}"/$(tar --list ${tar_options} | grep 'bin/javac' | tr '/' '\n' | tail -3 | head -1) else target="${workspace}"/$(tar --list ${tar_options} | head -2 | tail -1 | cut -f 2 -d '/' -)/Contents/Home fi verbose "Set target to: ${target}" else echo "Using custom target: ${target}" if [[ "$OSTYPE" != "darwin"* ]]; then mkdir --parents "${target}" tar --extract ${tar_options} -C "${target}" --strip-components=1 else mkdir -p "${target}" tar --extract ${tar_options} -C "${target}" --strip-components=4 fi fi if [[ ${verbose} == true ]]; then echo "Content of target directory:" ls "${target}" echo "Content of release file:" [[ ! -f "${target}/release" ]] || cat "${target}/release" fi # Link to system certificates # https://openjdk.java.net/jeps/319 # https://bugs.openjdk.java.net/browse/JDK-8196141 if [[ ${cacerts} == true ]]; then local directory="${target}/lib/security/cacerts" if [[ -f "${directory}" ]]; then mv "${directory}" "${directory}.jdk" ln -s /etc/ssl/certs/java/cacerts "${directory}" else verbose "Directory ${directory} doesn't exist, didn't link system CA certificates." fi fi } function main() { initialize parse_options "$@" say "$script_name $script_version" prepare_variables if [[ ${silent} == false ]]; then print_variables; fi if [[ ${dry} == true ]]; then exit 0; fi download_and_extract_and_set_target export JAVA_HOME=$(cd "${target}"; pwd) export PATH=${JAVA_HOME}/bin:$PATH if [[ ${silent} == false ]]; then java -Xmx100m -version; fi if [[ ${emit_java_home} == true ]]; then echo "${JAVA_HOME}"; fi } main "$@" ================================================ FILE: osx-toolchains.xml ================================================ paths java /Library/Java/JavaVirtualMachines/jdk-11.0.11.jdk/Contents/Home/bin ================================================ FILE: pom.xml ================================================ 4.0.0 UTF-8 --add-modules=ALL-MODULE-PATH false true 1.12.0 0.45.0 5.1.9 7.0.5 2.3.232 5.4.24.Final 3.29.2-GA 0.11.4.1 4.13.2 2.25.1 3.0.1 3.2.5 5.0.0-rc17 1.5.4 3.7.7 4.14.0 2.5.4 42.7.7 0.16.0 2.0.17 1.20.3 com.zaxxer HikariCP 7.0.3-SNAPSHOT bundle HikariCP Ultimate JDBC Connection Pool https://github.com/brettwooldridge/HikariCP 2013 Zaxxer.com https://github.com/brettwooldridge scm:git:https://github.com/brettwooldridge/HikariCP.git scm:git:https://github.com/brettwooldridge/HikariCP.git https://github.com/brettwooldridge/HikariCP HEAD ossrh https://oss.sonatype.org/content/repositories/snapshots https://github.com/brettwooldridge/HikariCP/issues The Apache Software License, Version 2.0 https://www.apache.org/licenses/LICENSE-2.0.txt repo Brett Wooldridge brett.wooldridge@gmail.com org.slf4j slf4j-api ${slf4j.version} org.apache.logging.log4j log4j-api ${log4j.version} test org.apache.logging.log4j log4j-core ${log4j.version} test org.testcontainers postgresql ${testcontainers.version} test org.apache.commons commons-compress 1.27.1 test org.apache.commons commons-lang3 3.18.0 test org.apache.commons commons-csv ${commons.csv.version} test org.mockito mockito-core ${mockito.version} test org.hamcrest hamcrest-core junit junit ${junit.version} test org.javassist javassist ${javassist.version} true io.micrometer micrometer-core ${micrometer.version} true org.postgresql postgresql ${postgresql.version} test org.hibernate hibernate-core ${hibernate.version} provided true jboss-logging org.jboss.logging jboss-logging-annotations org.jboss.logging io.dropwizard.metrics metrics-core ${metrics.version} provided true io.dropwizard.metrics metrics-healthchecks ${metrics.version} provided true io.dropwizard.metrics5 metrics-core ${metrics5.version} provided true io.prometheus simpleclient ${simpleclient.version} provided true simple-jndi simple-jndi ${jndi.version} test org.apache.logging.log4j log4j-slf4j2-impl ${log4j.version} test javax.inject javax.inject 1 test org.apache.felix org.apache.felix.framework ${felix.version} test org.ops4j.pax.exam pax-exam-container-native ${pax.exam.version} test org.ops4j.pax.exam pax-exam-junit4 ${pax.exam.version} test org.ops4j.pax.exam pax-exam-link-assembly ${pax.exam.version} test org.ops4j.pax.exam pax-exam-link-mvn ${pax.exam.version} test org.ops4j.pax.url pax-url-aether ${pax.url.version} test org.ops4j.pax.url pax-url-reference ${pax.url.version} test com.h2database h2 ${h2.version} test org.apache.maven.plugins maven-resources-plugin 3.3.1 org.apache.maven.plugins maven-install-plugin 3.1.4 org.apache.maven.plugins maven-clean-plugin 3.2.0 org.apache.maven.plugins maven-compiler-plugin 3.10.1 11 11 -Xlint org.apache.maven.plugins maven-deploy-plugin 2.8.2 org.apache.maven.plugins maven-gpg-plugin 3.0.1 org.apache.maven.plugins maven-release-plugin 2.5.3 org.sonatype.plugins nexus-staging-maven-plugin 1.6.12 org.apache.maven.plugins maven-surefire-plugin 3.0.0-M8 ${surefireArgLine} ${sureFireOptions11} ${sureFireForks11} false org.apache.maven.plugins maven-source-plugin 3.0.1 true attach-sources jar-no-fork org.apache.maven.plugins maven-javadoc-plugin 3.11.2 public true 1024m true **\/module-info.java **\/HikariConfigurationUtil.java **\/HikariConnectionProvider.java **\/Prometheus*Tracker.java **\/Micrometer*Tracker.java **\/Dropwizard*Tracker.java **\/Coda*Tracker.java true attach-javadoc package jar org.apache.maven.plugins maven-enforcer-plugin 3.4.1 enforce-maven enforce (3.0.0,) maven-dependency-plugin 2.8 generate-sources build-classpath maven.compile.classpath org.apache.maven.plugins maven-javadoc-plugin org.codehaus.mojo exec-maven-plugin 1.6.0 true compile exec java -cp ${project.build.outputDirectory}${path.separator}${maven.compile.classpath} com.zaxxer.hikari.util.JavassistProxyFactory ${project.basedir} io.fabric8 docker-maven-plugin ${docker.maven.plugin.fabric8.version} default true db postgres:16 password database system is ready to accept connections DB yellow start pre-integration-test build start stop post-integration-test stop org.jacoco jacoco-maven-plugin 0.8.8 prepare-agent ${project.build.directory}/coverage-reports/jacoco.exec surefireArgLine **/com/zaxxer/hikari/util/JavassistProxyFactory* **/com/zaxxer/hikari/pool/HikariProxy* **/com/zaxxer/hikari/metrics/** report test report ${project.build.directory}/coverage-reports/jacoco.exec **/com/zaxxer/hikari/pool/HikariProxy* **/com/zaxxer/hikari/metrics/** org.apache.maven.plugins maven-failsafe-plugin 3.0.0-M3 integration-test verify org.apache.felix maven-bundle-plugin ${felix.bundle.plugin.version} true ${artifact.classifier} com.zaxxer.hikari HikariCP com.zaxxer.hikari, com.zaxxer.hikari.hibernate, com.zaxxer.hikari.metrics, com.zaxxer.hikari.metrics.dropwizard, com.zaxxer.hikari.metrics.micrometer, com.zaxxer.hikari.metrics.prometheus com.zaxxer.hikari.* target/classes/module-info.class <_exportcontents> com.zaxxer.hikari.pool, com.zaxxer.hikari.util javax.management, javax.naming, javax.naming.spi, javax.sql, javax.sql.rowset, javax.sql.rowset.serial, javax.sql.rowset.spi, com.codahale.metrics;resolution:=optional, com.codahale.metrics.health;resolution:=optional, io.dropwizard.metrics5;resolution:=optional, io.micrometer.core.instrument;resolution:=optional, org.slf4j;version="[1.6,2)", org.hibernate;resolution:=optional, org.hibernate.cfg;resolution:=optional, org.hibernate.engine.jdbc.connections.spi;resolution:=optional, org.hibernate.service;resolution:=optional, org.hibernate.service.spi;resolution:=optional ${project.groupId}.${project.artifactId} * manifest felix true pax.exam.framework felix felix none org.apache.felix org.apache.felix.framework ${felix.version} test release performRelease true org.apache.maven.plugins maven-source-plugin 3.3.0 attach-sources jar-no-fork org.apache.maven.plugins maven-gpg-plugin sign-artifacts verify sign org.sonatype.plugins nexus-staging-maven-plugin true ${autoReleaseStagedArtifacts} https://ossrh-staging-api.central.sonatype.com ossrh ================================================ FILE: src/main/java/com/zaxxer/hikari/HikariConfig.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari; import com.codahale.metrics.health.HealthCheckRegistry; import com.zaxxer.hikari.metrics.MetricsTrackerFactory; import com.zaxxer.hikari.util.Credentials; 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.*; import java.lang.reflect.Modifier; import java.security.AccessControlException; import java.sql.Connection; import java.util.Properties; import java.util.TreeSet; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicReference; import static com.zaxxer.hikari.util.UtilityElf.*; 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); private static final char[] ID_CHARACTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); private static final long CONNECTION_TIMEOUT = SECONDS.toMillis(30); private static final long VALIDATION_TIMEOUT = SECONDS.toMillis(5); private static final long SOFT_TIMEOUT_FLOOR = Long.getLong("com.zaxxer.hikari.timeoutMs.floor", 250L); private static final long IDLE_TIMEOUT = MINUTES.toMillis(10); private static final long MAX_LIFETIME = MINUTES.toMillis(30); private static final long DEFAULT_KEEPALIVE_TIME = MINUTES.toMillis(2); private static final int DEFAULT_POOL_SIZE = 10; private static boolean unitTest = false; // Properties changeable at runtime through the HikariConfigMXBean // private volatile String catalog; private volatile long connectionTimeout; private volatile long validationTimeout; private volatile long idleTimeout; private volatile long leakDetectionThreshold; private volatile long maxLifetime; private volatile int maxPoolSize; private volatile int minIdle; private final AtomicReference credentials = new AtomicReference<>(Credentials.of(null, null)); // Properties NOT changeable at runtime // private long initializationFailTimeout; private String connectionInitSql; private String connectionTestQuery; private String credentialsProviderClassName; private String dataSourceClassName; private String dataSourceJndiName; private String driverClassName; private String exceptionOverrideClassName; private SQLExceptionOverride exceptionOverride; private String jdbcUrl; private String poolName; private String schema; private String transactionIsolationName; private boolean isAutoCommit; private boolean isReadOnly; private boolean isIsolateInternalQueries; private boolean isRegisterMbeans; private boolean isAllowPoolSuspension; private HikariCredentialsProvider credentialsProvider; private DataSource dataSource; private Properties dataSourceProperties; private ThreadFactory threadFactory; private ScheduledExecutorService scheduledExecutor; private MetricsTrackerFactory metricsTrackerFactory; private Object metricRegistry; private Object healthCheckRegistry; private Properties healthCheckProperties; private long keepaliveTime; private volatile boolean sealed; /** * Default constructor *

* If the System property {@code hikari.configurationFile} is set, * then the default constructor will attempt to load the specified configuration file *

* {@link #HikariConfig(String propertyFileName)} can be similarly used * instead of using the system property */ public HikariConfig() { dataSourceProperties = new Properties(); healthCheckProperties = new Properties(); minIdle = -1; maxPoolSize = DEFAULT_POOL_SIZE; maxLifetime = MAX_LIFETIME; connectionTimeout = CONNECTION_TIMEOUT; validationTimeout = VALIDATION_TIMEOUT; idleTimeout = IDLE_TIMEOUT; initializationFailTimeout = 1; isAutoCommit = true; keepaliveTime = DEFAULT_KEEPALIVE_TIME; var systemProp = System.getProperty("hikaricp.configurationFile"); if (systemProp != null) { loadProperties(systemProp); } } /** * Construct a HikariConfig from the specified properties object. * * @param properties the name of the property file */ public HikariConfig(Properties properties) { this(); PropertyElf.setTargetFromProperties(this, properties); } /** * Construct a HikariConfig from the specified property file name. propertyFileName * will first be treated as a path in the file-system, and if that fails the * Class.getResourceAsStream(propertyFileName) will be tried. * * @param propertyFileName the name of the property file */ public HikariConfig(String propertyFileName) { this(); loadProperties(propertyFileName); } // *********************************************************************** // HikariConfigMXBean methods // *********************************************************************** /** {@inheritDoc} */ @Override public String getCatalog() { return catalog; } /** {@inheritDoc} */ @Override public void setCatalog(String catalog) { this.catalog = catalog; } /** {@inheritDoc} */ @Override public long getConnectionTimeout() { return connectionTimeout; } /** {@inheritDoc} */ @Override public void setConnectionTimeout(long connectionTimeoutMs) { if (connectionTimeoutMs == 0) { this.connectionTimeout = Integer.MAX_VALUE; } else if (connectionTimeoutMs < SOFT_TIMEOUT_FLOOR) { throw new IllegalArgumentException("connectionTimeout cannot be less than " + SOFT_TIMEOUT_FLOOR + "ms"); } else { this.connectionTimeout = connectionTimeoutMs; } } /** {@inheritDoc} */ @Override public long getIdleTimeout() { return idleTimeout; } /** {@inheritDoc} */ @Override public void setIdleTimeout(long idleTimeoutMs) { if (idleTimeoutMs < 0) { throw new IllegalArgumentException("idleTimeout cannot be negative"); } this.idleTimeout = idleTimeoutMs; } /** {@inheritDoc} */ @Override public long getLeakDetectionThreshold() { return leakDetectionThreshold; } /** {@inheritDoc} */ @Override public void setLeakDetectionThreshold(long leakDetectionThresholdMs) { this.leakDetectionThreshold = leakDetectionThresholdMs; } /** {@inheritDoc} */ @Override public long getMaxLifetime() { return maxLifetime; } /** {@inheritDoc} */ @Override public void setMaxLifetime(long maxLifetimeMs) { this.maxLifetime = maxLifetimeMs; } /** {@inheritDoc} */ @Override public int getMaximumPoolSize() { return maxPoolSize; } /** {@inheritDoc} */ @Override public void setMaximumPoolSize(int maxPoolSize) { if (maxPoolSize < 1) { throw new IllegalArgumentException("maxPoolSize cannot be less than 1"); } this.maxPoolSize = maxPoolSize; } /** {@inheritDoc} */ @Override public int getMinimumIdle() { return minIdle; } /** {@inheritDoc} */ @Override public void setMinimumIdle(int minIdle) { if (minIdle < 0) { throw new IllegalArgumentException("minimumIdle cannot be negative"); } this.minIdle = minIdle; } /** * Get the default password to use for DataSource.getConnection(username, password) calls. * @return the password */ public String getPassword() { return credentials.get().getPassword(); } /** * Set the default password to use for DataSource.getConnection(username, password) calls. * @param password the password */ @Override public void setPassword(String password) { credentials.updateAndGet(current -> Credentials.of(current.getUsername(), password)); } /** * Get the default username used for DataSource.getConnection(username, password) calls. * * @return the username */ public String getUsername() { return credentials.get().getUsername(); } /** * Set the default username used for DataSource.getConnection(username, password) calls. * * @param username the username */ @Override public void setUsername(String username) { credentials.updateAndGet(current -> Credentials.of(username, current.getPassword())); } /** * Atomically set the default username and password to use for DataSource.getConnection(username, password) calls. * * @param credentials the username and password pair */ @Override public void setCredentials(final Credentials credentials) { this.credentials.set(credentials); } /** * Atomically get the default username and password to use for DataSource.getConnection(username, password) calls. * * @return the username and password pair */ public Credentials getCredentials() { return credentials.get(); } /** {@inheritDoc} */ @Override public long getValidationTimeout() { return validationTimeout; } /** {@inheritDoc} */ @Override public void setValidationTimeout(long validationTimeoutMs) { if (validationTimeoutMs < SOFT_TIMEOUT_FLOOR) { throw new IllegalArgumentException("validationTimeout cannot be less than " + SOFT_TIMEOUT_FLOOR + "ms"); } this.validationTimeout = validationTimeoutMs; } // *********************************************************************** // All other configuration methods // *********************************************************************** /** * Get the SQL query to be executed to test the validity of connections. * * @return the SQL query string, or null */ public String getConnectionTestQuery() { return connectionTestQuery; } /** * Set the SQL query to be executed to test the validity of connections. Using * the JDBC4 Connection.isValid() method to test connection validity can * be more efficient on some databases and is recommended. * * @param connectionTestQuery a SQL query string */ public void setConnectionTestQuery(String connectionTestQuery) { checkIfSealed(); this.connectionTestQuery = connectionTestQuery; } /** * Get the SQL string that will be executed on all new connections when they are * created, before they are added to the pool. * * @return the SQL to execute on new connections, or null */ public String getConnectionInitSql() { return connectionInitSql; } /** * Set the SQL string that will be executed on all new connections when they are * created, before they are added to the pool. If this query fails, it will be * treated as a failed connection attempt. * * @param connectionInitSql the SQL to execute on new connections */ public void setConnectionInitSql(String connectionInitSql) { checkIfSealed(); this.connectionInitSql = connectionInitSql; } /** * Get the {@link DataSource} that has been explicitly specified to be wrapped by the * pool. * * @return the {@link DataSource} instance, or null */ public DataSource getDataSource() { return dataSource; } /** * Set a {@link DataSource} for the pool to explicitly wrap. This setter is not * available through property file based initialization. * * @param dataSource a specific {@link DataSource} to be wrapped by the pool */ public void setDataSource(DataSource dataSource) { checkIfSealed(); this.dataSource = dataSource; } /** * Get the name of the JDBC {@link DataSource} class used to create Connections. * * @return the fully qualified name of the JDBC {@link DataSource} class */ public String getDataSourceClassName() { return dataSourceClassName; } /** * Set the fully qualified class name of the JDBC {@link DataSource} that will be used create Connections. * * @param className the fully qualified name of the JDBC {@link DataSource} class */ public void setDataSourceClassName(String className) { checkIfSealed(); this.dataSourceClassName = className; } /** * Add a property (name/value pair) that will be used to configure the {@link DataSource}/{@link java.sql.Driver}. *

* In the case of a {@link DataSource}, the property names will be translated to Java setters following the Java Bean * naming convention. For example, the property {@code cachePrepStmts} will translate into {@code setCachePrepStmts()} * with the {@code value} passed as a parameter. *

* In the case of a {@link java.sql.Driver}, the property will be added to a {@link Properties} instance that will * be passed to the driver during {@link java.sql.Driver#connect(String, Properties)} calls. * * @param propertyName the name of the property * @param value the value to be used by the DataSource/Driver */ public void addDataSourceProperty(String propertyName, Object value) { checkIfSealed(); dataSourceProperties.put(propertyName, value); } public String getDataSourceJNDI() { return this.dataSourceJndiName; } public void setDataSourceJNDI(String jndiDataSource) { checkIfSealed(); this.dataSourceJndiName = jndiDataSource; } public Properties getDataSourceProperties() { return dataSourceProperties; } public void setDataSourceProperties(Properties dsProperties) { checkIfSealed(); dataSourceProperties.putAll(dsProperties); } public String getDriverClassName() { return driverClassName; } public void setDriverClassName(String driverClassName) { checkIfSealed(); try { createInstance(driverClassName, java.sql.Driver.class); this.driverClassName = driverClassName; } catch (Exception e) { throw new RuntimeException("Failed to load driver class " + driverClassName, e); } } public String getJdbcUrl() { return jdbcUrl; } public void setJdbcUrl(String jdbcUrl) { checkIfSealed(); this.jdbcUrl = jdbcUrl; } /** * Get the default auto-commit behavior of connections in the pool. * * @return the default auto-commit behavior of connections */ public boolean isAutoCommit() { return isAutoCommit; } /** * Set the default auto-commit behavior of connections in the pool. * * @param isAutoCommit the desired auto-commit default for connections */ public void setAutoCommit(boolean isAutoCommit) { checkIfSealed(); this.isAutoCommit = isAutoCommit; } /** * Get the pool suspension behavior (allowed or disallowed). * * @return the pool suspension behavior */ public boolean isAllowPoolSuspension() { return isAllowPoolSuspension; } /** * Set whether or not pool suspension is allowed. There is a performance * impact when pool suspension is enabled. Unless you need it (for a * redundancy system for example) do not enable it. * * @param isAllowPoolSuspension the desired pool suspension allowance */ public void setAllowPoolSuspension(boolean isAllowPoolSuspension) { checkIfSealed(); this.isAllowPoolSuspension = isAllowPoolSuspension; } /** * Get the pool initialization failure timeout. See {@code #setInitializationFailTimeout(long)} * for details. * * @return the number of milliseconds before the pool initialization fails * @see HikariConfig#setInitializationFailTimeout(long) */ public long getInitializationFailTimeout() { return initializationFailTimeout; } /** * Set the pool initialization failure timeout. This setting applies to pool * initialization when {@link HikariDataSource} is constructed with a {@link HikariConfig}, * or when {@link HikariDataSource} is constructed using the no-arg constructor * and {@link HikariDataSource#getConnection()} is called. *

    *
  • 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 value of zero will not 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.
  • *
  • A value less than zero will 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.
  • *
* 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 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) { checkIfSealed(); this.initializationFailTimeout = initializationFailTimeout; } /** * Determine whether internal pool queries, principally aliveness checks, will be isolated in their own transaction * via {@link Connection#rollback()}. Defaults to {@code false}. * * @return {@code true} if internal pool queries are isolated, {@code false} if not */ public boolean isIsolateInternalQueries() { return isIsolateInternalQueries; } /** * Configure whether internal pool queries, principally aliveness checks, will be isolated in their own transaction * via {@link Connection#rollback()}. Defaults to {@code false}. * * @param isolate {@code true} if internal pool queries should be isolated, {@code false} if not */ public void setIsolateInternalQueries(boolean isolate) { checkIfSealed(); this.isIsolateInternalQueries = isolate; } public MetricsTrackerFactory getMetricsTrackerFactory() { return metricsTrackerFactory; } public void setMetricsTrackerFactory(MetricsTrackerFactory metricsTrackerFactory) { if (metricRegistry != null) { throw new IllegalStateException("cannot use setMetricsTrackerFactory() and setMetricRegistry() together"); } this.metricsTrackerFactory = metricsTrackerFactory; } /** * Get the MetricRegistry instance to use for registration of metrics used by HikariCP. Default is {@code null}. * * @return the MetricRegistry instance that will be used */ public Object getMetricRegistry() { return metricRegistry; } /** * Set a MetricRegistry instance to use for registration of metrics used by HikariCP. * * @param metricRegistry the MetricRegistry instance to use */ public void setMetricRegistry(Object metricRegistry) { if (metricsTrackerFactory != null) { throw new IllegalStateException("cannot use setMetricRegistry() and setMetricsTrackerFactory() together"); } if (metricRegistry != null) { metricRegistry = getObjectOrPerformJndiLookup(metricRegistry); if (!safeIsAssignableFrom(metricRegistry, "com.codahale.metrics.MetricRegistry") && !(safeIsAssignableFrom(metricRegistry, "io.dropwizard.metrics5.MetricRegistry")) && !(safeIsAssignableFrom(metricRegistry, "io.micrometer.core.instrument.MeterRegistry"))) { throw new IllegalArgumentException("Class must be instance of com.codahale.metrics.MetricRegistry, " + "io.dropwizard.metrics5.MetricRegistry, or io.micrometer.core.instrument.MeterRegistry"); } } this.metricRegistry = metricRegistry; } /** * Get the HealthCheckRegistry that will be used for registration of health checks by HikariCP. Currently only * Codahale/DropWizard is supported for health checks. * * @return the HealthCheckRegistry instance that will be used */ public Object getHealthCheckRegistry() { return healthCheckRegistry; } /** * Set the HealthCheckRegistry that will be used for registration of health checks by HikariCP. Currently only * Codahale/DropWizard is supported for health checks. Default is {@code null}. * * @param healthCheckRegistry the HealthCheckRegistry to be used */ public void setHealthCheckRegistry(Object healthCheckRegistry) { checkIfSealed(); if (healthCheckRegistry != null) { healthCheckRegistry = getObjectOrPerformJndiLookup(healthCheckRegistry); if (!(healthCheckRegistry instanceof HealthCheckRegistry)) { throw new IllegalArgumentException("Class must be an instance of com.codahale.metrics.health.HealthCheckRegistry"); } } this.healthCheckRegistry = healthCheckRegistry; } public Properties getHealthCheckProperties() { return healthCheckProperties; } public void setHealthCheckProperties(Properties healthCheckProperties) { checkIfSealed(); this.healthCheckProperties.putAll(healthCheckProperties); } public void addHealthCheckProperty(String key, String value) { checkIfSealed(); healthCheckProperties.setProperty(key, value); } /** * This property controls the keepalive interval for a connection in the pool. An in-use connection will never be * tested by the keepalive thread, only when it is idle will it be tested. * * @return the interval in which connections will be tested for aliveness, thus keeping them alive by the act of checking. Value is in milliseconds, default is 0 (disabled). */ public long getKeepaliveTime() { return keepaliveTime; } /** * This property controls the keepalive interval for a connection in the pool. An in-use connection will never be * tested by the keepalive thread, only when it is idle will it be tested. * * @param keepaliveTimeMs the interval in which connections will be tested for aliveness, thus keeping them alive by the act of checking. Value is in milliseconds, default is 0 (disabled). */ public void setKeepaliveTime(long keepaliveTimeMs) { this.keepaliveTime = keepaliveTimeMs; } /** * Determine whether the Connections in the pool are in read-only mode. * * @return {@code true} if the Connections in the pool are read-only, {@code false} if not */ public boolean isReadOnly() { return isReadOnly; } /** * Configures the Connections to be added to the pool as read-only Connections. * * @param readOnly {@code true} if the Connections in the pool are read-only, {@code false} if not */ public void setReadOnly(boolean readOnly) { checkIfSealed(); this.isReadOnly = readOnly; } /** * Determine whether HikariCP will self-register {@link HikariConfigMXBean} and {@link HikariPoolMXBean} instances * in JMX. * * @return {@code true} if HikariCP will register MXBeans, {@code false} if it will not */ public boolean isRegisterMbeans() { return isRegisterMbeans; } /** * Configures whether HikariCP self-registers the {@link HikariConfigMXBean} and {@link HikariPoolMXBean} in JMX. * * @param register {@code true} if HikariCP should register MXBeans, {@code false} if it should not */ public void setRegisterMbeans(boolean register) { checkIfSealed(); this.isRegisterMbeans = register; } /** {@inheritDoc} */ @Override public String getPoolName() { return poolName; } /** * Set the name of the connection pool. This is primarily used in logging and JMX management consoles * to identify pools and pool configurations * * @param poolName the name of the connection pool to use */ public void setPoolName(String poolName) { checkIfSealed(); this.poolName = poolName; } /** * Get the ScheduledExecutorService used for housekeeping. * * @return the executor */ public ScheduledExecutorService getScheduledExecutor() { return scheduledExecutor; } /** * Set the ScheduledExecutorService used for housekeeping. * * @param executor the ScheduledExecutorService */ public void setScheduledExecutor(ScheduledExecutorService executor) { checkIfSealed(); 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. * * @param schema the name of the default schema */ public void setSchema(String schema) { checkIfSealed(); this.schema = schema; } /** * Get the class name of the {@link HikariCredentialsProvider} that will be used to get credentials at runtime. * * @return the class name of the credentials provider * @see HikariCredentialsProvider */ public String getCredentialsProviderClassName() { return credentialsProviderClassName; } /** * Set the class name of the {@link HikariCredentialsProvider} that will be used to get credentials at runtime. Use this method * or provide a {@link HikariCredentialsProvider} instance via the {@link #setCredentialsProvider(HikariCredentialsProvider)} method. * * @param credentialsProviderClassName the class name of the credentials provider * @see HikariCredentialsProvider */ public void setCredentialsProviderClassName(String credentialsProviderClassName) { checkIfSealed(); try { this.credentialsProvider = createInstance(credentialsProviderClassName, HikariCredentialsProvider.class); this.exceptionOverrideClassName = credentialsProviderClassName; } catch (Exception e) { throw new RuntimeException("Failed to instantiate class " + credentialsProviderClassName, e); } } /** * Get the {@link HikariCredentialsProvider} instance created by {@link #setCredentialsProviderClassName(String)} or specified by * {@link #setCredentialsProvider(HikariCredentialsProvider)}. * * @return the HikariCredentialsProvider instance, or null * @see HikariCredentialsProvider */ public HikariCredentialsProvider getCredentialsProvider() { return credentialsProvider; } /** * Set a user supplied {@link HikariCredentialsProvider} instance. If this method is used, then the {@link #setCredentialsProviderClassName(String)} * method should not be used. The {@link HikariCredentialsProvider} instance will be used to get credentials at runtime. * * @param credentialsProvider a user supplied HikariCredentialsProvider instance * @see HikariCredentialsProvider */ public void setCredentialsProvider(HikariCredentialsProvider credentialsProvider) { checkIfSealed(); this.credentialsProvider = credentialsProvider; } /** * Get the user supplied SQLExceptionOverride class name. * * @return the user supplied SQLExceptionOverride class name * @see SQLExceptionOverride */ public String getExceptionOverrideClassName() { return this.exceptionOverrideClassName; } /** * Set the user supplied SQLExceptionOverride class name. * * @param exceptionOverrideClassName the user supplied SQLExceptionOverride class name * @see SQLExceptionOverride */ public void setExceptionOverrideClassName(String exceptionOverrideClassName) { checkIfSealed(); try { this.exceptionOverride = createInstance(exceptionOverrideClassName, SQLExceptionOverride.class); this.exceptionOverrideClassName = exceptionOverrideClassName; } catch (Exception e) { throw new RuntimeException("Failed to instantiate class " + exceptionOverrideClassName, e); } } /** * Get the SQLExceptionOverride instance created by {@link #setExceptionOverrideClassName(String)} or specified by * {@link #setExceptionOverride(SQLExceptionOverride)}. * * @return the SQLExceptionOverride instance, or null * @see SQLExceptionOverride */ public SQLExceptionOverride getExceptionOverride() { return this.exceptionOverride; } /** * Set the user supplied SQLExceptionOverride instance. * * @param exceptionOverride the user supplied SQLExceptionOverride instance * @see SQLExceptionOverride */ public void setExceptionOverride(SQLExceptionOverride exceptionOverride) { checkIfSealed(); this.exceptionOverride = exceptionOverride; } /** * Set the default transaction isolation level. The specified value is the * constant name from the Connection class, eg. * TRANSACTION_REPEATABLE_READ. * * @param isolationLevel the name of the isolation level */ public void setTransactionIsolation(String isolationLevel) { checkIfSealed(); this.transactionIsolationName = isolationLevel; } /** * Get the thread factory used to create threads. * * @return the thread factory (may be null, in which case the default thread factory is used) */ public ThreadFactory getThreadFactory() { return threadFactory; } /** * Set the thread factory to be used to create threads. * * @param threadFactory the thread factory (setting to null causes the default thread factory to be used) */ public void setThreadFactory(ThreadFactory threadFactory) { checkIfSealed(); this.threadFactory = threadFactory; } void seal() { this.sealed = true; } /** * Copies the state of {@code this} into {@code other}. * * @param other Other {@link HikariConfig} to copy the state to. */ @SuppressWarnings({"rawtypes", "unchecked"}) public void copyStateTo(HikariConfig other) { for (var field : HikariConfig.class.getDeclaredFields()) { try { if (!Modifier.isFinal(field.getModifiers())) { field.setAccessible(true); field.set(other, field.get(this)); } else if (field.getType().isAssignableFrom(AtomicReference.class)) { ((AtomicReference) field.get(other)).set(((AtomicReference) field.get(this)).get()); } } catch (Exception e) { throw new RuntimeException("Failed to copy HikariConfig state: " + e.getMessage(), e); } } other.sealed = false; } // *********************************************************************** // Private methods // *********************************************************************** @SuppressWarnings("StatementWithEmptyBody") public void validate() { if (poolName == null) { poolName = generatePoolName(); } else if (isRegisterMbeans && poolName.contains(":")) { throw new IllegalArgumentException("poolName cannot contain ':' when used with JMX"); } // treat empty property as null //noinspection NonAtomicOperationOnVolatileField catalog = getNullIfEmpty(catalog); connectionInitSql = getNullIfEmpty(connectionInitSql); connectionTestQuery = getNullIfEmpty(connectionTestQuery); transactionIsolationName = getNullIfEmpty(transactionIsolationName); dataSourceClassName = getNullIfEmpty(dataSourceClassName); dataSourceJndiName = getNullIfEmpty(dataSourceJndiName); driverClassName = getNullIfEmpty(driverClassName); jdbcUrl = getNullIfEmpty(jdbcUrl); // Check Data Source Options if (dataSource != null) { if (dataSourceClassName != null) { LOGGER.warn("{} - using dataSource and ignoring dataSourceClassName.", poolName); } } else if (dataSourceClassName != null) { if (driverClassName != null) { LOGGER.error("{} - cannot use driverClassName and dataSourceClassName together.", poolName); // NOTE: This exception text is referenced by a Spring Boot FailureAnalyzer, it should not be // changed without first notifying the Spring Boot developers. throw new IllegalStateException("cannot use driverClassName and dataSourceClassName together."); } else if (jdbcUrl != null) { LOGGER.warn("{} - using dataSourceClassName and ignoring jdbcUrl.", poolName); } } else if (jdbcUrl != null || dataSourceJndiName != null) { // ok } else if (driverClassName != null) { LOGGER.error("{} - jdbcUrl is required with driverClassName.", poolName); throw new IllegalArgumentException("jdbcUrl is required with driverClassName."); } else { LOGGER.error("{} - dataSource or dataSourceClassName or jdbcUrl is required.", poolName); throw new IllegalArgumentException("dataSource or dataSourceClassName or jdbcUrl is required."); } validateNumerics(); if (LOGGER.isDebugEnabled() || unitTest) { logConfiguration(); } } private void validateNumerics() { if (maxLifetime != 0 && maxLifetime < SECONDS.toMillis(30)) { LOGGER.warn("{} - maxLifetime is less than 30000ms, setting to default {}ms.", poolName, MAX_LIFETIME); maxLifetime = MAX_LIFETIME; } // keepalive time must larger than 30 seconds if (keepaliveTime != 0 && keepaliveTime < SECONDS.toMillis(30)) { LOGGER.warn("{} - keepaliveTime is less than 30000ms, disabling it.", poolName); keepaliveTime = 0L; } // keepalive time must be less than maxLifetime (if maxLifetime is enabled) if (keepaliveTime != 0 && maxLifetime != 0 && keepaliveTime >= maxLifetime) { LOGGER.warn("{} - keepaliveTime is greater than or equal to maxLifetime, disabling it.", poolName); keepaliveTime = 0L; } if (leakDetectionThreshold > 0 && !unitTest) { if (leakDetectionThreshold < SECONDS.toMillis(2) || (leakDetectionThreshold > maxLifetime && maxLifetime > 0)) { LOGGER.warn("{} - leakDetectionThreshold is less than 2000ms or more than maxLifetime, disabling it.", poolName); leakDetectionThreshold = 0; } } if (connectionTimeout < SOFT_TIMEOUT_FLOOR) { LOGGER.warn("{} - connectionTimeout is less than {}ms, setting to {}ms.", poolName, SOFT_TIMEOUT_FLOOR, CONNECTION_TIMEOUT); connectionTimeout = CONNECTION_TIMEOUT; } if (validationTimeout < SOFT_TIMEOUT_FLOOR) { LOGGER.warn("{} - validationTimeout is less than {}ms, setting to {}ms.", poolName, SOFT_TIMEOUT_FLOOR, VALIDATION_TIMEOUT); validationTimeout = VALIDATION_TIMEOUT; } if (minIdle < 0 || minIdle > maxPoolSize) { minIdle = maxPoolSize; } if (idleTimeout + SECONDS.toMillis(1) > maxLifetime && maxLifetime > 0 && minIdle < maxPoolSize) { LOGGER.warn("{} - idleTimeout is close to or more than maxLifetime, disabling it.", poolName); idleTimeout = 0; } else if (idleTimeout != 0 && idleTimeout < SECONDS.toMillis(10) && minIdle < maxPoolSize) { LOGGER.warn("{} - idleTimeout is less than 10000ms, setting to default {}ms.", poolName, IDLE_TIMEOUT); idleTimeout = IDLE_TIMEOUT; } else if (idleTimeout != IDLE_TIMEOUT && idleTimeout != 0 && minIdle == maxPoolSize) { LOGGER.warn("{} - idleTimeout has been set but has no effect because the pool is operating as a fixed size pool.", poolName); } } private void checkIfSealed() { if (sealed) throw new IllegalStateException("The configuration of the pool is sealed once started. Use HikariConfigMXBean for runtime changes."); } private void logConfiguration() { LOGGER.debug("{} - configuration:", poolName); final var propertyNames = new TreeSet<>(PropertyElf.getPropertyNames(HikariConfig.class)); for (var prop : propertyNames) { try { var value = PropertyElf.getProperty(prop, this); if ("dataSourceProperties".equals(prop)) { var dsProps = PropertyElf.copyProperties(dataSourceProperties); dsProps.setProperty("password", ""); value = dsProps; } if ("initializationFailTimeout".equals(prop) && initializationFailTimeout == Long.MAX_VALUE) { value = "infinite"; } else if ("transactionIsolation".equals(prop) && transactionIsolationName == null) { value = "default"; } else if (prop.matches("scheduledExecutorService|threadFactory") && value == null) { value = "internal"; } else if (prop.contains("jdbcUrl") && value instanceof String) { value = maskPasswordInJdbcUrl((String) value); } else if (prop.contains("password")) { value = ""; } else if (value instanceof String) { value = "\"" + value + "\""; // quote to see lead/trailing spaces is any } else if (value == null) { value = "none"; } LOGGER.debug("{}{}", (prop + "................................................").substring(0, 32), value); } catch (Exception e) { // continue } } } private void loadProperties(String propertyFileName) { try (final var is = openPropertiesInputStream(propertyFileName)) { if (is != null) { var props = new Properties(); props.load(is); PropertyElf.setTargetFromProperties(this, props); } else { throw new IllegalArgumentException("Cannot find property file: " + propertyFileName); } } catch (IOException io) { throw new RuntimeException("Failed to read property file", io); } } private InputStream openPropertiesInputStream(String propertyFileName) throws FileNotFoundException { final var propFile = new File(propertyFileName); if (propFile.isFile()) { return new FileInputStream(propFile); } var propertiesInputStream = this.getClass().getResourceAsStream(propertyFileName); if (propertiesInputStream == null) { propertiesInputStream = this.getClass().getClassLoader().getResourceAsStream(propertyFileName); } return propertiesInputStream; } private String generatePoolName() { final var prefix = "HikariPool-"; try { // Pool number is global to the VM to avoid overlapping pool numbers in classloader scoped environments synchronized (System.getProperties()) { final var next = String.valueOf(Integer.getInteger("com.zaxxer.hikari.pool_number", 0) + 1); System.setProperty("com.zaxxer.hikari.pool_number", next); return prefix + next; } } catch (AccessControlException e) { // The SecurityManager didn't allow us to read/write system properties // so just generate a random pool number instead final var random = ThreadLocalRandom.current(); final var buf = new StringBuilder(prefix); for (var i = 0; i < 4; i++) { buf.append(ID_CHARACTERS[random.nextInt(62)]); } LOGGER.info("assigned random pool name '{}' (security manager prevented access to system properties)", buf); return buf.toString(); } } private Object getObjectOrPerformJndiLookup(Object object) { if (object instanceof String) { try { var initCtx = new InitialContext(); return initCtx.lookup((String) object); } catch (NamingException e) { throw new IllegalArgumentException(e); } } return object; } } ================================================ FILE: src/main/java/com/zaxxer/hikari/HikariConfigMXBean.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari; import com.zaxxer.hikari.util.Credentials; /** * The javax.management MBean for a Hikari pool configuration. * * @author Brett Wooldridge */ public interface HikariConfigMXBean { /** * Get the maximum number of milliseconds that a client will wait for a connection from the pool. If this * time is exceeded without a connection becoming available, a SQLException will be thrown from * {@link javax.sql.DataSource#getConnection()}. * * @return the connection timeout in milliseconds */ long getConnectionTimeout(); /** * Set the maximum number of milliseconds that a client will wait for a connection from the pool. If this * time is exceeded without a connection becoming available, a SQLException will be thrown from * {@link javax.sql.DataSource#getConnection()}. * * @param connectionTimeoutMs the connection timeout in milliseconds */ void setConnectionTimeout(long connectionTimeoutMs); /** * Get the maximum number of milliseconds that the pool will wait for a connection to be validated as * alive. * * @return the validation timeout in milliseconds */ long getValidationTimeout(); /** * Sets the maximum number of milliseconds that the pool will wait for a connection to be validated as * alive. * * @param validationTimeoutMs the validation timeout in milliseconds */ void setValidationTimeout(long validationTimeoutMs); /** * This property controls the maximum amount of time (in milliseconds) that a connection is allowed to sit * idle in the pool. Whether a connection is retired as idle or not is subject to a maximum variation of +30 * seconds, and average variation of +15 seconds. A connection will never be retired as idle before this timeout. * A value of 0 means that idle connections are never removed from the pool. * * @return the idle timeout in milliseconds */ long getIdleTimeout(); /** * This property controls the maximum amount of time (in milliseconds) that a connection is allowed to sit * idle in the pool. Whether a connection is retired as idle or not is subject to a maximum variation of +30 * seconds, and average variation of +15 seconds. A connection will never be retired as idle before this timeout. * A value of 0 means that idle connections are never removed from the pool. * * @param idleTimeoutMs the idle timeout in milliseconds */ void setIdleTimeout(long idleTimeoutMs); /** * This property controls the amount of time that a connection can be out of the pool before a message is * logged indicating a possible connection leak. A value of 0 means leak detection is disabled. * * @return the connection leak detection threshold in milliseconds */ long getLeakDetectionThreshold(); /** * This property controls the amount of time that a connection can be out of the pool before a message is * logged indicating a possible connection leak. A value of 0 means leak detection is disabled. * * @param leakDetectionThresholdMs the connection leak detection threshold in milliseconds */ void setLeakDetectionThreshold(long leakDetectionThresholdMs); /** * This property controls the maximum lifetime of a connection in the pool. When a connection reaches this * timeout, even if recently used, it will be retired from the pool. An in-use connection will never be * retired, only when it is idle will it be removed. * * @return the maximum connection lifetime in milliseconds */ long getMaxLifetime(); /** * This property controls the maximum lifetime of a connection in the pool. When a connection reaches this * timeout, even if recently used, it will be retired from the pool. An in-use connection will never be * retired, only when it is idle will it be removed. * * @param maxLifetimeMs the maximum connection lifetime in milliseconds */ void setMaxLifetime(long maxLifetimeMs); /** * The property controls the minimum number of idle connections that HikariCP tries to maintain in the pool, * including both idle and in-use connections. If the idle connections dip below this value, HikariCP will * make a best effort to restore them quickly and efficiently. * * @return the minimum number of connections in the pool */ int getMinimumIdle(); /** * The property controls the minimum number of idle connections that HikariCP tries to maintain in the pool, * including both idle and in-use connections. If the idle connections dip below this value, HikariCP will * make a best effort to restore them quickly and efficiently. * * @param minIdle the minimum number of idle connections in the pool to maintain */ void setMinimumIdle(int minIdle); /** * The property controls the maximum number of connections that HikariCP will keep in the pool, * including both idle and in-use connections. * * @return the maximum number of connections in the pool */ int getMaximumPoolSize(); /** * The property controls the maximum size that the pool is allowed to reach, including both idle and in-use * connections. Basically this value will determine the maximum number of actual connections to the database * backend. *

* When the pool reaches this size, and no idle connections are available, calls to getConnection() will * block for up to connectionTimeout milliseconds before timing out. * * @param maxPoolSize the maximum number of connections in the pool */ void setMaximumPoolSize(int maxPoolSize); /** * Set the password used for authentication. Changing this at runtime will apply to new connections only. * Altering this at runtime only works for DataSource-based connections, not Driver-class or JDBC URL-based * connections. * * @param password the database password */ void setPassword(String password); /** * Set the username used for authentication. Changing this at runtime will apply to new connections only. * Altering this at runtime only works for DataSource-based connections, not Driver-class or JDBC URL-based * connections. * * @param username the database username */ void setUsername(String username); /** * Set the username and password used for authentication. Changing this at runtime will apply to new * connections only. Altering this at runtime only works for DataSource-based connections, not Driver-class * or JDBC URL-based connections. * * @param credentials the database username and password pair */ void setCredentials(Credentials credentials); /** * The name of the connection pool. * * @return the name of the connection pool */ String getPoolName(); /** * Get the default catalog name to be set on connections. * * @return the default catalog name */ String getCatalog(); /** * Set the default catalog name to be set on connections. *

* WARNING: THIS VALUE SHOULD ONLY BE CHANGED WHILE THE POOL IS SUSPENDED, AFTER CONNECTIONS HAVE BEEN EVICTED. * * @param catalog the catalog name, or null */ void setCatalog(String catalog); } ================================================ FILE: src/main/java/com/zaxxer/hikari/HikariCredentialsProvider.java ================================================ /* * Copyright (C) 2025 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari; import com.zaxxer.hikari.util.Credentials; /** * Users can implement this interface to provide credentials for HikariCP. * This is useful when credentials need to be dynamically generated or retrieved * at runtime, rather than being hardcoded in the configuration. */ public interface HikariCredentialsProvider { /** * This method is called to retrieve the credentials for HikariCP. * * @return a {@link Credentials} object containing the username and password */ Credentials getCredentials(); } ================================================ FILE: src/main/java/com/zaxxer/hikari/HikariDataSource.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari; import com.zaxxer.hikari.metrics.MetricsTrackerFactory; import com.zaxxer.hikari.pool.HikariPool; import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.sql.DataSource; import java.io.Closeable; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.concurrent.atomic.AtomicBoolean; import static com.zaxxer.hikari.pool.HikariPool.POOL_NORMAL; /** * The HikariCP pooled DataSource. * * @author Brett Wooldridge */ public class HikariDataSource extends HikariConfig implements DataSource, Closeable { private static final Logger LOGGER = LoggerFactory.getLogger(HikariDataSource.class); private final AtomicBoolean isShutdown = new AtomicBoolean(); private final HikariPool fastPathPool; private volatile HikariPool pool; /** * Default constructor. Setters are used to configure the pool. Using * this constructor vs. {@link #HikariDataSource(HikariConfig)} will * result in {@link #getConnection()} performance that is slightly lower * due to lazy initialization checks. * * The first call to {@link #getConnection()} starts the pool. Once the pool * is started, the configuration is "sealed" and no further configuration * changes are possible -- except via {@link HikariConfigMXBean} methods. */ public HikariDataSource() { fastPathPool = null; } /** * Construct a HikariDataSource with the specified configuration. The * {@link HikariConfig} is copied and the pool is started by invoking this * constructor. * * The {@link HikariConfig} can be modified without affecting the HikariDataSource * and used to initialize another HikariDataSource instance. * * @param configuration a HikariConfig instance */ public HikariDataSource(HikariConfig configuration) { configuration.validate(); configuration.copyStateTo(this); LOGGER.info("{} - Starting...", configuration.getPoolName()); pool = fastPathPool = new HikariPool(this); LOGGER.info("{} - Start completed.", configuration.getPoolName()); this.seal(); } // *********************************************************************** // DataSource methods // *********************************************************************** /** {@inheritDoc} */ @Override public Connection getConnection() throws SQLException { if (isClosed()) { throw new SQLException("HikariDataSource " + this + " has been closed."); } if (fastPathPool != null) { return fastPathPool.getConnection(); } // See http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java HikariPool result = pool; if (result == null) { synchronized (this) { result = pool; if (result == null) { validate(); LOGGER.info("{} - Starting...", getPoolName()); try { pool = result = new HikariPool(this); this.seal(); } catch (PoolInitializationException pie) { if (pie.getCause() instanceof SQLException) { throw (SQLException) pie.getCause(); } else { throw pie; } } LOGGER.info("{} - Start completed.", getPoolName()); } } } return result.getConnection(); } /** {@inheritDoc} */ @Override public Connection getConnection(String username, String password) throws SQLException { throw new SQLFeatureNotSupportedException(); } /** {@inheritDoc} */ @Override public PrintWriter getLogWriter() throws SQLException { HikariPool p = pool; return (p != null ? p.getUnwrappedDataSource().getLogWriter() : null); } /** {@inheritDoc} */ @Override public void setLogWriter(PrintWriter out) throws SQLException { var p = pool; if (p != null) { p.getUnwrappedDataSource().setLogWriter(out); } } /** {@inheritDoc} */ @Override public void setLoginTimeout(int seconds) throws SQLException { var p = pool; if (p != null) { p.getUnwrappedDataSource().setLoginTimeout(seconds); } } /** {@inheritDoc} */ @Override public int getLoginTimeout() throws SQLException { var p = pool; return (p != null ? p.getUnwrappedDataSource().getLoginTimeout() : 0); } /** {@inheritDoc} */ @Override public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException { throw new SQLFeatureNotSupportedException(); } /** {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public T unwrap(Class iface) throws SQLException { if (iface.isInstance(this)) { return (T) this; } var p = pool; if (p != null) { final var unwrappedDataSource = p.getUnwrappedDataSource(); if (iface.isInstance(unwrappedDataSource)) { return (T) unwrappedDataSource; } if (unwrappedDataSource != null) { return unwrappedDataSource.unwrap(iface); } } throw new SQLException("Wrapped DataSource is not an instance of " + iface); } /** {@inheritDoc} */ @Override public boolean isWrapperFor(Class iface) throws SQLException { if (iface.isInstance(this)) { return true; } var p = pool; if (p != null) { final var unwrappedDataSource = p.getUnwrappedDataSource(); if (iface.isInstance(unwrappedDataSource)) { return true; } if (unwrappedDataSource != null) { return unwrappedDataSource.isWrapperFor(iface); } } return false; } // *********************************************************************** // HikariConfigMXBean methods // *********************************************************************** /** {@inheritDoc} */ @Override public void setMetricRegistry(Object metricRegistry) { var isAlreadySet = getMetricRegistry() != null; super.setMetricRegistry(metricRegistry); var p = pool; if (p != null) { if (isAlreadySet) { throw new IllegalStateException("MetricRegistry can only be set one time"); } else { p.setMetricRegistry(super.getMetricRegistry()); } } } /** {@inheritDoc} */ @Override public void setMetricsTrackerFactory(MetricsTrackerFactory metricsTrackerFactory) { var isAlreadySet = getMetricsTrackerFactory() != null; super.setMetricsTrackerFactory(metricsTrackerFactory); var p = pool; if (p != null) { if (isAlreadySet) { throw new IllegalStateException("MetricsTrackerFactory can only be set one time"); } else { p.setMetricsTrackerFactory(super.getMetricsTrackerFactory()); } } } /** {@inheritDoc} */ @Override public void setHealthCheckRegistry(Object healthCheckRegistry) { var isAlreadySet = getHealthCheckRegistry() != null; super.setHealthCheckRegistry(healthCheckRegistry); var p = pool; if (p != null) { if (isAlreadySet) { throw new IllegalStateException("HealthCheckRegistry can only be set one time"); } else { p.setHealthCheckRegistry(super.getHealthCheckRegistry()); } } } // *********************************************************************** // HikariCP-specific methods // *********************************************************************** /** * Returns {@code true} if the pool as been started and is not suspended or shutdown. * * @return {@code true} if the pool as been started and is not suspended or shutdown. */ public boolean isRunning() { return pool != null && pool.poolState == POOL_NORMAL; } /** * 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. * * @param connection the connection to evict from the pool */ public void evictConnection(Connection connection) { HikariPool p; if (!isClosed() && (p = pool) != null && connection.getClass().getName().startsWith("com.zaxxer.hikari")) { p.evictConnection(connection); } } /** * Shutdown the DataSource and its associated pool. */ @Override public void close() { if (isShutdown.getAndSet(true)) { return; } var p = pool; if (p != null) { try { LOGGER.info("{} - Shutdown initiated...", getPoolName()); p.shutdown(); LOGGER.info("{} - Shutdown completed.", getPoolName()); } catch (InterruptedException e) { LOGGER.warn("{} - Interrupted during closing", getPoolName(), e); Thread.currentThread().interrupt(); } } } /** * Determine whether the HikariDataSource has been closed. * * @return true if the HikariDataSource has been closed, false otherwise */ public boolean isClosed() { return isShutdown.get(); } /** {@inheritDoc} */ @Override public String toString() { return "HikariDataSource (" + pool + ")"; } } ================================================ FILE: src/main/java/com/zaxxer/hikari/HikariJNDIFactory.java ================================================ /* * Copyright (C) 2013,2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari; import com.zaxxer.hikari.util.PropertyElf; import javax.naming.*; import javax.naming.spi.ObjectFactory; import javax.sql.DataSource; import java.util.Hashtable; import java.util.Properties; /** * A JNDI factory that produces HikariDataSource instances. * * @author Brett Wooldridge */ public class HikariJNDIFactory implements ObjectFactory { @Override synchronized public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception { // We only know how to deal with javax.naming.Reference that specify a class name of "javax.sql.DataSource" if (obj instanceof Reference && "javax.sql.DataSource".equals(((Reference) obj).getClassName())) { var ref = (Reference) obj; var hikariPropSet = PropertyElf.getPropertyNames(HikariConfig.class); var properties = new Properties(); var enumeration = ref.getAll(); while (enumeration.hasMoreElements()) { var element = enumeration.nextElement(); var type = element.getType(); if (type.startsWith("dataSource.") || hikariPropSet.contains(type)) { properties.setProperty(type, element.getContent().toString()); } } return createDataSource(properties, nameCtx); } return null; } private DataSource createDataSource(final Properties properties, final Context context) throws NamingException { var jndiName = properties.getProperty("dataSourceJNDI"); if (jndiName != null) { return lookupJndiDataSource(properties, context, jndiName); } return new HikariDataSource(new HikariConfig(properties)); } private DataSource lookupJndiDataSource(final Properties properties, final Context context, final String jndiName) throws NamingException { if (context == null) { throw new RuntimeException("JNDI context does not found for dataSourceJNDI : " + jndiName); } var jndiDS = (DataSource) context.lookup(jndiName); if (jndiDS == null) { final var ic = new InitialContext(); jndiDS = (DataSource) ic.lookup(jndiName); ic.close(); } if (jndiDS != null) { var config = new HikariConfig(properties); config.setDataSource(jndiDS); return new HikariDataSource(config); } return null; } } ================================================ FILE: src/main/java/com/zaxxer/hikari/HikariPoolMXBean.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari; import javax.sql.DataSource; /** * The javax.management MBean for a Hikari pool instance. * * @author Brett Wooldridge */ public interface HikariPoolMXBean { /** * Get the number of currently idle connections in the pool. *

* The return value is extremely transient and is a point-in-time measurement. Therefore, due to a time * difference between invoking this method and {@link #getActiveConnections()}, it is possible for the sum * of idle plus active connections to be either less than or greater than the value returned by * {@link #getTotalConnections()}. * * @return the current number of idle connections in the pool */ int getIdleConnections(); /** * Get the number of currently active connections in the pool. *

* The return value is extremely transient and is a point-in-time measurement. Therefore, due to a time * difference between invoking this method and {@link #getIdleConnections()}, it is possible for the sum * of idle plus active connections to be either less than or greater than the value returned by * {@link #getTotalConnections()}. * * @return the current number of active (in-use) connections in the pool */ int getActiveConnections(); /** * Get the total number of connections currently in the pool. The return value is transient and is a * point-in-time measurement. * * @return the total number of connections in the pool */ int getTotalConnections(); /** * Get the number of threads awaiting connections from the pool. The return value is extremely transient and is * a point-in-time measurement. * * @return the number of threads awaiting a connection from the pool */ int getThreadsAwaitingConnection(); /** * Evict currently idle connections from the pool, and mark active (in-use) connections for eviction when they are * returned to the pool. */ void softEvictConnections(); /** * Suspend the pool. When the pool is suspended, threads calling {@link DataSource#getConnection()} will be * blocked with no timeout until the pool is resumed via the {@link #resumePool()} method. *
* This method has no effect unless the {@link HikariConfig#setAllowPoolSuspension(boolean)} method or equivalent * property has been set to {@code true}. */ void suspendPool(); /** * Resume the pool. Enables connection borrowing to resume on a pool that has been suspended via the * {@link #suspendPool()} method. *
* This method has no effect unless the {@link HikariConfig#setAllowPoolSuspension(boolean)} method or equivalent * property has been set to {@code true}. */ void resumePool(); } ================================================ FILE: src/main/java/com/zaxxer/hikari/SQLExceptionOverride.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari; import java.sql.SQLException; /** * Users can implement this interface to override the default SQLException handling * of HikariCP. When a SQLException is thrown from JDBC execution methods, the * SQLState and error code will be checked to determine if the connection should * be evicted from the pool. *

* By supplying an implementation of this interface, users can override the default * handling of SQLExceptions. The {@link #adjudicate(SQLException)} method will be called * with the SQLException that was thrown. If the method returns {@link Override#CONTINUE_EVICT} * the customary built-in handling will occur. If the method returns {@link Override#DO_NOT_EVICT} * the eviction will be elided. If the method returns {@link Override#MUST_EVICT} the eviction will * be evicted regardless of the SQLState or error code. */ public interface SQLExceptionOverride { enum Override { CONTINUE_EVICT, DO_NOT_EVICT, MUST_EVICT } /** * This method is called when a SQLException is thrown from a JDBC method. * * @param sqlException the SQLException that was thrown * @return an {@link Override} value indicating how eviction should proceed */ default Override adjudicate(final SQLException sqlException) { return Override.CONTINUE_EVICT; } } ================================================ FILE: src/main/java/com/zaxxer/hikari/hibernate/HikariConfigurationUtil.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.hibernate; import java.util.Map; import java.util.Properties; import org.hibernate.cfg.AvailableSettings; import com.zaxxer.hikari.HikariConfig; /** * Utility class to map Hibernate properties to HikariCP configuration properties. * * @author Brett Wooldridge, Luca Burgazzoli */ public class HikariConfigurationUtil { public static final String CONFIG_PREFIX = "hibernate.hikari."; public static final String CONFIG_PREFIX_DATASOURCE = "hibernate.hikari.dataSource."; /** * Create/load a HikariConfig from Hibernate properties. * * @param props a map of Hibernate properties * @return a HikariConfig */ @SuppressWarnings("rawtypes") public static HikariConfig loadConfiguration(Map props) { Properties hikariProps = new Properties(); copyProperty(AvailableSettings.ISOLATION, props, "transactionIsolation", hikariProps); copyProperty(AvailableSettings.AUTOCOMMIT, props, "autoCommit", hikariProps); copyProperty(AvailableSettings.DRIVER, props, "driverClassName", hikariProps); copyProperty(AvailableSettings.URL, props, "jdbcUrl", hikariProps); copyProperty(AvailableSettings.USER, props, "username", hikariProps); copyProperty(AvailableSettings.PASS, props, "password", hikariProps); for (Object keyo : props.keySet()) { String key = (String) keyo; if (key.startsWith(CONFIG_PREFIX)) { hikariProps.setProperty(key.substring(CONFIG_PREFIX.length()), (String) props.get(key)); } } return new HikariConfig(hikariProps); } @SuppressWarnings("rawtypes") private static void copyProperty(String srcKey, Map src, String dstKey, Properties dst) { if (src.containsKey(srcKey)) { dst.setProperty(dstKey, (String) src.get(srcKey)); } } } ================================================ FILE: src/main/java/com/zaxxer/hikari/hibernate/HikariConnectionProvider.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.hibernate; import java.sql.Connection; import java.sql.SQLException; import java.util.Map; import org.hibernate.HibernateException; import org.hibernate.Version; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.service.UnknownUnwrapTypeException; import org.hibernate.service.spi.Configurable; import org.hibernate.service.spi.Stoppable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; /** * Connection provider for Hibernate 4.3. * * @author Brett Wooldridge, Luca Burgazzoli */ public class HikariConnectionProvider implements ConnectionProvider, Configurable, Stoppable { private static final long serialVersionUID = -9131625057941275711L; private static final Logger LOGGER = LoggerFactory.getLogger(HikariConnectionProvider.class); /** * HikariCP configuration. */ private HikariConfig hcfg; /** * HikariCP data source. */ private HikariDataSource hds; // ************************************************************************* // // ************************************************************************* /** * c-tor */ public HikariConnectionProvider() { this.hcfg = null; this.hds = null; if (Version.getVersionString().substring(0, 5).compareTo("4.3.6") >= 1) { LOGGER.warn("com.zaxxer.hikari.hibernate.HikariConnectionProvider has been deprecated for versions of " + "Hibernate 4.3.6 and newer. Please switch to org.hibernate.hikaricp.internal.HikariCPConnectionProvider."); } } // ************************************************************************* // Configurable // ************************************************************************* @SuppressWarnings("rawtypes") @Override public void configure(Map props) throws HibernateException { try { LOGGER.debug("Configuring HikariCP"); this.hcfg = HikariConfigurationUtil.loadConfiguration(props); this.hds = new HikariDataSource(this.hcfg); } catch (Exception e) { throw new HibernateException(e); } LOGGER.debug("HikariCP Configured"); } // ************************************************************************* // ConnectionProvider // ************************************************************************* @Override public Connection getConnection() throws SQLException { Connection conn = null; if (this.hds != null) { conn = this.hds.getConnection(); } return conn; } @Override public void closeConnection(Connection conn) throws SQLException { conn.close(); } @Override public boolean supportsAggressiveRelease() { return false; } @Override @SuppressWarnings("rawtypes") public boolean isUnwrappableAs(Class unwrapType) { return ConnectionProvider.class.equals(unwrapType) || HikariConnectionProvider.class.isAssignableFrom(unwrapType); } @Override @SuppressWarnings("unchecked") public T unwrap(Class unwrapType) { if ( ConnectionProvider.class.equals( unwrapType ) || HikariConnectionProvider.class.isAssignableFrom( unwrapType ) ) { return (T) this; } else if ( DataSource.class.isAssignableFrom( unwrapType ) ) { return (T) this.hds; } else { throw new UnknownUnwrapTypeException( unwrapType ); } } // ************************************************************************* // Stoppable // ************************************************************************* @Override public void stop() { this.hds.close(); } } ================================================ FILE: src/main/java/com/zaxxer/hikari/metrics/IMetricsTracker.java ================================================ /* * Copyright (C) 2017 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics; /** * @author Brett Wooldridge */ public interface IMetricsTracker extends AutoCloseable { default void recordConnectionCreatedMillis(long connectionCreatedMillis) {} default void recordConnectionAcquiredNanos(final long elapsedAcquiredNanos) {} default void recordConnectionUsageMillis(final long elapsedBorrowedMillis) {} default void recordConnectionTimeout() {} @Override default void close() {} } ================================================ FILE: src/main/java/com/zaxxer/hikari/metrics/MetricsTracker.java ================================================ /* * Copyright (C) 2013,2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics; /** * This class only supports realtime, not historical metrics. * * @author Brett Wooldridge */ @Deprecated public class MetricsTracker implements IMetricsTracker { } ================================================ FILE: src/main/java/com/zaxxer/hikari/metrics/MetricsTrackerFactory.java ================================================ /* * Copyright (C) 2013,2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics; public interface MetricsTrackerFactory { /** * Create an instance of an IMetricsTracker. * * @param poolName the name of the pool * @param poolStats a PoolStats instance to use * @return a IMetricsTracker implementation instance */ IMetricsTracker create(String poolName, PoolStats poolStats); } ================================================ FILE: src/main/java/com/zaxxer/hikari/metrics/PoolStats.java ================================================ /* * Copyright (C) 2015 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics; import static com.zaxxer.hikari.util.ClockSource.currentTime; import static com.zaxxer.hikari.util.ClockSource.plusMillis; import java.util.concurrent.atomic.AtomicLong; /** * * @author Brett Wooldridge */ public abstract class PoolStats { private final AtomicLong reloadAt; private final long timeoutMs; protected volatile int totalConnections; protected volatile int idleConnections; protected volatile int activeConnections; protected volatile int pendingThreads; protected volatile int maxConnections; protected volatile int minConnections; public PoolStats(final long timeoutMs) { this.timeoutMs = timeoutMs; this.reloadAt = new AtomicLong(); } public int getTotalConnections() { if (shouldLoad()) { update(); } return totalConnections; } public int getIdleConnections() { if (shouldLoad()) { update(); } return idleConnections; } public int getActiveConnections() { if (shouldLoad()) { update(); } return activeConnections; } public int getPendingThreads() { if (shouldLoad()) { update(); } return pendingThreads; } public int getMaxConnections() { if (shouldLoad()) { update(); } return maxConnections; } public int getMinConnections() { if (shouldLoad()) { update(); } return minConnections; } protected abstract void update(); private boolean shouldLoad() { for (; ; ) { final var now = currentTime(); final var reloadTime = reloadAt.get(); if (reloadTime > now) { return false; } else if (reloadAt.compareAndSet(reloadTime, plusMillis(now, timeoutMs))) { return true; } } } } ================================================ FILE: src/main/java/com/zaxxer/hikari/metrics/dropwizard/CodaHaleMetricsTracker.java ================================================ /* * Copyright (C) 2013,2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics.dropwizard; import java.util.concurrent.TimeUnit; import com.codahale.metrics.Gauge; import com.codahale.metrics.Histogram; import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; import com.zaxxer.hikari.metrics.IMetricsTracker; import com.zaxxer.hikari.metrics.PoolStats; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_CATEGORY; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_ACTIVE_CONNECTIONS; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_CONNECT; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_IDLE_CONNECTIONS; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_MAX_CONNECTIONS; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_MIN_CONNECTIONS; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_PENDING_CONNECTIONS; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_TIMEOUT_RATE; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_TOTAL_CONNECTIONS; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_USAGE; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_WAIT; public final class CodaHaleMetricsTracker implements IMetricsTracker { private final String poolName; private final Timer connectionObtainTimer; private final Histogram connectionUsage; private final Histogram connectionCreation; private final Meter connectionTimeoutMeter; private final MetricRegistry registry; CodaHaleMetricsTracker(final String poolName, final PoolStats poolStats, final MetricRegistry registry) { this.poolName = poolName; this.registry = registry; this.connectionObtainTimer = registry.timer(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_WAIT)); this.connectionUsage = registry.histogram(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_USAGE)); this.connectionCreation = registry.histogram(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_CONNECT)); this.connectionTimeoutMeter = registry.meter(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_TIMEOUT_RATE)); registry.register(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_TOTAL_CONNECTIONS), (Gauge) poolStats::getTotalConnections); registry.register(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_IDLE_CONNECTIONS), (Gauge) poolStats::getIdleConnections); registry.register(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_ACTIVE_CONNECTIONS), (Gauge) poolStats::getActiveConnections); registry.register(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_PENDING_CONNECTIONS), (Gauge) poolStats::getPendingThreads); registry.register(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_MAX_CONNECTIONS), (Gauge) poolStats::getMaxConnections); registry.register(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_MIN_CONNECTIONS), (Gauge) poolStats::getMinConnections); } /** {@inheritDoc} */ @Override public void close() { registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_WAIT)); registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_USAGE)); registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_CONNECT)); registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_TIMEOUT_RATE)); registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_TOTAL_CONNECTIONS)); registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_IDLE_CONNECTIONS)); registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_ACTIVE_CONNECTIONS)); registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_PENDING_CONNECTIONS)); registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_MAX_CONNECTIONS)); registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_MIN_CONNECTIONS)); } /** {@inheritDoc} */ @Override public void recordConnectionAcquiredNanos(final long elapsedAcquiredNanos) { connectionObtainTimer.update(elapsedAcquiredNanos, TimeUnit.NANOSECONDS); } /** {@inheritDoc} */ @Override public void recordConnectionUsageMillis(final long elapsedBorrowedMillis) { connectionUsage.update(elapsedBorrowedMillis); } @Override public void recordConnectionTimeout() { connectionTimeoutMeter.mark(); } @Override public void recordConnectionCreatedMillis(long connectionCreatedMillis) { connectionCreation.update(connectionCreatedMillis); } public Timer getConnectionAcquisitionTimer() { return connectionObtainTimer; } public Histogram getConnectionDurationHistogram() { return connectionUsage; } public Histogram getConnectionCreationHistogram() { return connectionCreation; } } ================================================ FILE: src/main/java/com/zaxxer/hikari/metrics/dropwizard/CodahaleHealthChecker.java ================================================ /* * Copyright (C) 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics.dropwizard; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; import com.codahale.metrics.health.HealthCheck; import com.codahale.metrics.health.HealthCheckRegistry; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.pool.HikariPool; import java.sql.Connection; import java.sql.SQLException; import java.util.concurrent.TimeUnit; /** * Provides Dropwizard HealthChecks. Two health checks are provided: *

    *
  • ConnectivityCheck
  • *
  • Connection99Percent
  • *
* The ConnectivityCheck will use the connectionTimeout, unless the health check property * connectivityCheckTimeoutMs is defined. However, if either the connectionTimeout * or the connectivityCheckTimeoutMs is 0 (infinite), a timeout of 10 seconds will be used. *

* The Connection99Percent health check will only be registered if the health check property * expected99thPercentileMs is defined and greater than 0. * * @author Brett Wooldridge */ public final class CodahaleHealthChecker { /** * Register Dropwizard health checks. * * @param pool the pool to register health checks for * @param hikariConfig the pool configuration * @param registry the HealthCheckRegistry into which checks will be registered */ public static void registerHealthChecks(final HikariPool pool, final HikariConfig hikariConfig, final HealthCheckRegistry registry) { final var healthCheckProperties = hikariConfig.getHealthCheckProperties(); final var checkTimeoutMs = Long.parseLong(healthCheckProperties.getProperty("connectivityCheckTimeoutMs", String.valueOf(hikariConfig.getConnectionTimeout()))); registry.register(MetricRegistry.name(hikariConfig.getPoolName(), "pool", "ConnectivityCheck"), new ConnectivityHealthCheck(pool, checkTimeoutMs)); final var expected99thPercentile = Long.parseLong(healthCheckProperties.getProperty("expected99thPercentileMs", "0")); final Object metricRegistryObj = hikariConfig.getMetricRegistry(); if (expected99thPercentile > 0 && metricRegistryObj instanceof MetricRegistry) { final var metricRegistry = (MetricRegistry) metricRegistryObj; var timers = metricRegistry.getTimers((name, metric) -> name.equals(MetricRegistry.name(hikariConfig.getPoolName(), "pool", "Wait"))); if (!timers.isEmpty()) { final var timer = timers.entrySet().iterator().next().getValue(); registry.register(MetricRegistry.name(hikariConfig.getPoolName(), "pool", "Connection99Percent"), new Connection99Percent(timer, expected99thPercentile)); } } } private CodahaleHealthChecker() { // private constructor } private static class ConnectivityHealthCheck extends HealthCheck { private final HikariPool pool; private final long checkTimeoutMs; ConnectivityHealthCheck(final HikariPool pool, final long checkTimeoutMs) { this.pool = pool; this.checkTimeoutMs = (checkTimeoutMs > 0 && checkTimeoutMs != Integer.MAX_VALUE ? checkTimeoutMs : TimeUnit.SECONDS.toMillis(10)); } /** {@inheritDoc} */ @Override protected Result check() throws Exception { try (Connection connection = pool.getConnection(checkTimeoutMs)) { return Result.healthy(); } catch (SQLException e) { return Result.unhealthy(e); } } } private static class Connection99Percent extends HealthCheck { private final Timer waitTimer; private final long expected99thPercentile; Connection99Percent(final Timer waitTimer, final long expected99thPercentile) { this.waitTimer = waitTimer; this.expected99thPercentile = expected99thPercentile; } /** {@inheritDoc} */ @Override protected Result check() throws Exception { final long the99thPercentile = TimeUnit.NANOSECONDS.toMillis(Math.round(waitTimer.getSnapshot().get99thPercentile())); return the99thPercentile <= expected99thPercentile ? Result.healthy() : Result.unhealthy("99th percentile connection wait time of %dms exceeds the threshold %dms", the99thPercentile, expected99thPercentile); } } } ================================================ FILE: src/main/java/com/zaxxer/hikari/metrics/dropwizard/CodahaleMetricsTrackerFactory.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics.dropwizard; import com.codahale.metrics.MetricRegistry; import com.zaxxer.hikari.metrics.IMetricsTracker; import com.zaxxer.hikari.metrics.MetricsTrackerFactory; import com.zaxxer.hikari.metrics.PoolStats; public final class CodahaleMetricsTrackerFactory implements MetricsTrackerFactory { private final MetricRegistry registry; public CodahaleMetricsTrackerFactory(MetricRegistry registry) { this.registry = registry; } public MetricRegistry getRegistry() { return registry; } @Override public IMetricsTracker create(String poolName, PoolStats poolStats) { return new CodaHaleMetricsTracker(poolName, poolStats, registry); } } ================================================ FILE: src/main/java/com/zaxxer/hikari/metrics/dropwizard/Dropwizard5MetricsTracker.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics.dropwizard; import java.util.concurrent.TimeUnit; import com.zaxxer.hikari.metrics.IMetricsTracker; import com.zaxxer.hikari.metrics.PoolStats; import io.dropwizard.metrics5.Gauge; import io.dropwizard.metrics5.Histogram; import io.dropwizard.metrics5.Meter; import io.dropwizard.metrics5.MetricRegistry; import io.dropwizard.metrics5.Timer; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_CATEGORY; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_ACTIVE_CONNECTIONS; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_CONNECT; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_IDLE_CONNECTIONS; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_MAX_CONNECTIONS; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_MIN_CONNECTIONS; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_PENDING_CONNECTIONS; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_TIMEOUT_RATE; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_TOTAL_CONNECTIONS; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_USAGE; import static com.zaxxer.hikari.metrics.dropwizard.DropwizardCommon.METRIC_NAME_WAIT; public class Dropwizard5MetricsTracker implements IMetricsTracker { private final String poolName; private final Timer connectionObtainTimer; private final Histogram connectionUsage; private final Histogram connectionCreation; private final Meter connectionTimeoutMeter; private final MetricRegistry registry; Dropwizard5MetricsTracker(final String poolName, final PoolStats poolStats, final MetricRegistry registry) { this.poolName = poolName; this.registry = registry; this.connectionObtainTimer = registry.timer(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_WAIT)); this.connectionUsage = registry.histogram(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_USAGE)); this.connectionCreation = registry.histogram(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_CONNECT)); this.connectionTimeoutMeter = registry.meter(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_TIMEOUT_RATE)); registry.register(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_TOTAL_CONNECTIONS), (Gauge) poolStats::getTotalConnections); registry.register(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_IDLE_CONNECTIONS), (Gauge) poolStats::getIdleConnections); registry.register(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_ACTIVE_CONNECTIONS), (Gauge) poolStats::getActiveConnections); registry.register(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_PENDING_CONNECTIONS), (Gauge) poolStats::getPendingThreads); registry.register(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_MAX_CONNECTIONS), (Gauge) poolStats::getMaxConnections); registry.register(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_MIN_CONNECTIONS), (Gauge) poolStats::getMinConnections); } /** {@inheritDoc} */ @Override public void close() { registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_WAIT)); registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_USAGE)); registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_CONNECT)); registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_TIMEOUT_RATE)); registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_TOTAL_CONNECTIONS)); registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_IDLE_CONNECTIONS)); registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_ACTIVE_CONNECTIONS)); registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_PENDING_CONNECTIONS)); registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_MAX_CONNECTIONS)); registry.remove(MetricRegistry.name(poolName, METRIC_CATEGORY, METRIC_NAME_MIN_CONNECTIONS)); } /** {@inheritDoc} */ @Override public void recordConnectionAcquiredNanos(final long elapsedAcquiredNanos) { connectionObtainTimer.update(elapsedAcquiredNanos, TimeUnit.NANOSECONDS); } /** {@inheritDoc} */ @Override public void recordConnectionUsageMillis(final long elapsedBorrowedMillis) { connectionUsage.update(elapsedBorrowedMillis); } @Override public void recordConnectionTimeout() { connectionTimeoutMeter.mark(); } @Override public void recordConnectionCreatedMillis(final long connectionCreatedMillis) { connectionCreation.update(connectionCreatedMillis); } public Timer getConnectionAcquisitionTimer() { return connectionObtainTimer; } public Histogram getConnectionDurationHistogram() { return connectionUsage; } public Histogram getConnectionCreationHistogram() { return connectionCreation; } } ================================================ FILE: src/main/java/com/zaxxer/hikari/metrics/dropwizard/Dropwizard5MetricsTrackerFactory.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics.dropwizard; import com.zaxxer.hikari.metrics.IMetricsTracker; import com.zaxxer.hikari.metrics.MetricsTrackerFactory; import com.zaxxer.hikari.metrics.PoolStats; import io.dropwizard.metrics5.MetricRegistry; public class Dropwizard5MetricsTrackerFactory implements MetricsTrackerFactory { private final MetricRegistry registry; public Dropwizard5MetricsTrackerFactory(final MetricRegistry registry) { this.registry = registry; } public MetricRegistry getRegistry() { return registry; } @Override public IMetricsTracker create(final String poolName, final PoolStats poolStats) { return new Dropwizard5MetricsTracker(poolName, poolStats, registry); } } ================================================ FILE: src/main/java/com/zaxxer/hikari/metrics/dropwizard/DropwizardCommon.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics.dropwizard; final class DropwizardCommon { private DropwizardCommon() { } static final String METRIC_CATEGORY = "pool"; static final String METRIC_NAME_WAIT = "Wait"; static final String METRIC_NAME_USAGE = "Usage"; static final String METRIC_NAME_CONNECT = "ConnectionCreation"; static final String METRIC_NAME_TIMEOUT_RATE = "ConnectionTimeoutRate"; static final String METRIC_NAME_TOTAL_CONNECTIONS = "TotalConnections"; static final String METRIC_NAME_IDLE_CONNECTIONS = "IdleConnections"; static final String METRIC_NAME_ACTIVE_CONNECTIONS = "ActiveConnections"; static final String METRIC_NAME_PENDING_CONNECTIONS = "PendingConnections"; static final String METRIC_NAME_MAX_CONNECTIONS = "MaxConnections"; static final String METRIC_NAME_MIN_CONNECTIONS = "MinConnections"; } ================================================ FILE: src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTracker.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics.micrometer; import com.zaxxer.hikari.metrics.IMetricsTracker; import com.zaxxer.hikari.metrics.PoolStats; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import java.util.concurrent.TimeUnit; /** * {@link IMetricsTracker Metrics tracker} for Micrometer. * HikariCP metrics can be configured in your application by applying a * {@link io.micrometer.core.instrument.config.MeterFilter MeterFilter} to metrics starting with * {@link #HIKARI_METRIC_NAME_PREFIX}. For example, to configure client-side calculated percentiles: * *

 *     new MeterFilter() {
 *       @Override
 *       public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) {
 *         if (id.getName().startsWith(MicrometerMetricsTracker.HIKARI_METRIC_NAME_PREFIX)) {
 *           return DistributionStatisticConfig.builder()
 *               .percentiles(0.5, 0.95)
 *               .build()
 *               .merge(config);
 *            }
 *         return config;
 *         }
 *      };
 * 
*/ @SuppressWarnings("ALL") public class MicrometerMetricsTracker implements IMetricsTracker { /** Prefix used for all HikariCP metric names. */ public static final String HIKARI_METRIC_NAME_PREFIX = "hikaricp"; private static final String METRIC_CATEGORY = "pool"; private static final String METRIC_NAME_WAIT = HIKARI_METRIC_NAME_PREFIX + ".connections.acquire"; private static final String METRIC_NAME_USAGE = HIKARI_METRIC_NAME_PREFIX + ".connections.usage"; private static final String METRIC_NAME_CONNECT = HIKARI_METRIC_NAME_PREFIX + ".connections.creation"; private static final String METRIC_NAME_TIMEOUT_RATE = HIKARI_METRIC_NAME_PREFIX + ".connections.timeout"; private static final String METRIC_NAME_TOTAL_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections"; private static final String METRIC_NAME_IDLE_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.idle"; private static final String METRIC_NAME_ACTIVE_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.active"; private static final String METRIC_NAME_PENDING_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.pending"; private static final String METRIC_NAME_MAX_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.max"; private static final String METRIC_NAME_MIN_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.min"; private final Timer connectionObtainTimer; private final Counter connectionTimeoutCounter; private final Timer connectionUsage; private final Timer connectionCreation; @SuppressWarnings("FieldCanBeLocal") private final Gauge totalConnectionGauge; @SuppressWarnings("FieldCanBeLocal") private final Gauge idleConnectionGauge; @SuppressWarnings("FieldCanBeLocal") private final Gauge activeConnectionGauge; @SuppressWarnings("FieldCanBeLocal") private final Gauge pendingConnectionGauge; @SuppressWarnings("FieldCanBeLocal") private final Gauge maxConnectionGauge; @SuppressWarnings("FieldCanBeLocal") private final Gauge minConnectionGauge; @SuppressWarnings("FieldCanBeLocal") private final MeterRegistry meterRegistry; @SuppressWarnings("FieldCanBeLocal") private final PoolStats poolStats; MicrometerMetricsTracker(final String poolName, final PoolStats poolStats, final MeterRegistry meterRegistry) { // poolStats must be held with a 'strong reference' even though it is never referenced within this class this.poolStats = poolStats; // DO NOT REMOVE this.meterRegistry = meterRegistry; this.connectionObtainTimer = Timer.builder(METRIC_NAME_WAIT) .description("Connection acquire time") .tags(METRIC_CATEGORY, poolName) .register(meterRegistry); this.connectionCreation = Timer.builder(METRIC_NAME_CONNECT) .description("Connection creation time") .tags(METRIC_CATEGORY, poolName) .register(meterRegistry); this.connectionUsage = Timer.builder(METRIC_NAME_USAGE) .description("Connection usage time") .tags(METRIC_CATEGORY, poolName) .register(meterRegistry); this.connectionTimeoutCounter = Counter.builder(METRIC_NAME_TIMEOUT_RATE) .description("Connection timeout total count") .tags(METRIC_CATEGORY, poolName) .register(meterRegistry); this.totalConnectionGauge = Gauge.builder(METRIC_NAME_TOTAL_CONNECTIONS, poolStats, PoolStats::getTotalConnections) .description("Total connections") .tags(METRIC_CATEGORY, poolName) .register(meterRegistry); this.idleConnectionGauge = Gauge.builder(METRIC_NAME_IDLE_CONNECTIONS, poolStats, PoolStats::getIdleConnections) .description("Idle connections") .tags(METRIC_CATEGORY, poolName) .register(meterRegistry); this.activeConnectionGauge = Gauge.builder(METRIC_NAME_ACTIVE_CONNECTIONS, poolStats, PoolStats::getActiveConnections) .description("Active connections") .tags(METRIC_CATEGORY, poolName) .register(meterRegistry); this.pendingConnectionGauge = Gauge.builder(METRIC_NAME_PENDING_CONNECTIONS, poolStats, PoolStats::getPendingThreads) .description("Pending threads") .tags(METRIC_CATEGORY, poolName) .register(meterRegistry); this.maxConnectionGauge = Gauge.builder(METRIC_NAME_MAX_CONNECTIONS, poolStats, PoolStats::getMaxConnections) .description("Max connections") .tags(METRIC_CATEGORY, poolName) .register(meterRegistry); this.minConnectionGauge = Gauge.builder(METRIC_NAME_MIN_CONNECTIONS, poolStats, PoolStats::getMinConnections) .description("Min connections") .tags(METRIC_CATEGORY, poolName) .register(meterRegistry); } /** {@inheritDoc} */ @Override public void recordConnectionAcquiredNanos(final long elapsedAcquiredNanos) { connectionObtainTimer.record(elapsedAcquiredNanos, TimeUnit.NANOSECONDS); } /** {@inheritDoc} */ @Override public void recordConnectionUsageMillis(final long elapsedBorrowedMillis) { connectionUsage.record(elapsedBorrowedMillis, TimeUnit.MILLISECONDS); } @Override public void recordConnectionTimeout() { connectionTimeoutCounter.increment(); } @Override public void recordConnectionCreatedMillis(long connectionCreatedMillis) { connectionCreation.record(connectionCreatedMillis, TimeUnit.MILLISECONDS); } @Override public void close() { meterRegistry.remove(connectionObtainTimer); meterRegistry.remove(connectionTimeoutCounter); meterRegistry.remove(connectionUsage); meterRegistry.remove(connectionCreation); meterRegistry.remove(totalConnectionGauge); meterRegistry.remove(idleConnectionGauge); meterRegistry.remove(activeConnectionGauge); meterRegistry.remove(pendingConnectionGauge); meterRegistry.remove(maxConnectionGauge); meterRegistry.remove(minConnectionGauge); } } ================================================ FILE: src/main/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerFactory.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics.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); } } ================================================ FILE: src/main/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollector.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics.prometheus; import com.zaxxer.hikari.metrics.PoolStats; import io.prometheus.client.Collector; import 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; /** *
{@code
 * HikariConfig config = new HikariConfig();
 * config.setMetricsTrackerFactory(new HikariCPCollector());
 * }
* or *
{@code
 * config.setMetricsTrackerFactory(new HikariCPCollector(new CollectorRegistry()));
 * }
*/ class HikariCPCollector extends Collector { private static final List LABEL_NAMES = Collections.singletonList("pool"); private final Map poolStatsMap = new ConcurrentHashMap<>(); @Override public List collect() { return Arrays.asList( 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), createGauge("hikaricp_max_connections", "Max connections", PoolStats::getMaxConnections), createGauge("hikaricp_min_connections", "Min connections", PoolStats::getMinConnections) ); } void add(String name, PoolStats poolStats) { poolStatsMap.put(name, poolStats); } void remove(String name) { poolStatsMap.remove(name); } private GaugeMetricFamily createGauge(String metric, String help, Function metricValueFunction) { var metricFamily = new GaugeMetricFamily(metric, help, LABEL_NAMES); poolStatsMap.forEach((k, v) -> metricFamily.addMetric( Collections.singletonList(k), metricValueFunction.apply(v) )); return metricFamily; } } ================================================ FILE: src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusHistogramMetricsTracker.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics.prometheus; import com.zaxxer.hikari.metrics.IMetricsTracker; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.Counter; import io.prometheus.client.Histogram; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import static com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory.*; import static com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory.RegistrationStatus.REGISTERED; /** * Alternative Prometheus metrics tracker using a Histogram instead of Summary *

* This is an alternative metrics tracker that doesn't use a {@link io.prometheus.client.Summary}. Summaries require * heavy locks that might cause performance issues. Source: https://github.com/prometheus/client_java/issues/328 * * @see PrometheusMetricsTracker */ class PrometheusHistogramMetricsTracker implements IMetricsTracker { private static final Counter CONNECTION_TIMEOUT_COUNTER = Counter.build() .name("hikaricp_connection_timeout_total") .labelNames("pool") .help("Connection timeout total count") .create(); private static final Histogram ELAPSED_ACQUIRED_HISTOGRAM = registerHistogram("hikaricp_connection_acquired_nanos", "Connection acquired time (ns)", 1_000); private static final Histogram ELAPSED_BORROWED_HISTOGRAM = registerHistogram("hikaricp_connection_usage_millis", "Connection usage (ms)", 1); private static final Histogram ELAPSED_CREATION_HISTOGRAM = registerHistogram("hikaricp_connection_creation_millis", "Connection creation (ms)", 1); private final Counter.Child connectionTimeoutCounterChild; private static Histogram registerHistogram(String name, String help, double bucketStart) { return Histogram.build() .name(name) .labelNames("pool") .help(help) .exponentialBuckets(bucketStart, 2.0, 11) .create(); } private final static Map registrationStatuses = new ConcurrentHashMap<>(); private final String poolName; private final HikariCPCollector hikariCPCollector; private final Histogram.Child elapsedAcquiredHistogramChild; private final Histogram.Child elapsedBorrowedHistogramChild; private final Histogram.Child elapsedCreationHistogramChild; PrometheusHistogramMetricsTracker(String poolName, CollectorRegistry collectorRegistry, HikariCPCollector hikariCPCollector) { registerMetrics(collectorRegistry); this.poolName = poolName; this.hikariCPCollector = hikariCPCollector; this.connectionTimeoutCounterChild = CONNECTION_TIMEOUT_COUNTER.labels(poolName); this.elapsedAcquiredHistogramChild = ELAPSED_ACQUIRED_HISTOGRAM.labels(poolName); this.elapsedBorrowedHistogramChild = ELAPSED_BORROWED_HISTOGRAM.labels(poolName); this.elapsedCreationHistogramChild = ELAPSED_CREATION_HISTOGRAM.labels(poolName); } private void registerMetrics(CollectorRegistry collectorRegistry) { if (registrationStatuses.putIfAbsent(collectorRegistry, REGISTERED) == null) { CONNECTION_TIMEOUT_COUNTER.register(collectorRegistry); ELAPSED_ACQUIRED_HISTOGRAM.register(collectorRegistry); ELAPSED_BORROWED_HISTOGRAM.register(collectorRegistry); ELAPSED_CREATION_HISTOGRAM.register(collectorRegistry); } } @Override public void recordConnectionAcquiredNanos(long elapsedAcquiredNanos) { elapsedAcquiredHistogramChild.observe(elapsedAcquiredNanos); } @Override public void recordConnectionUsageMillis(long elapsedBorrowedMillis) { elapsedBorrowedHistogramChild.observe(elapsedBorrowedMillis); } @Override public void recordConnectionCreatedMillis(long connectionCreatedMillis) { elapsedCreationHistogramChild.observe(connectionCreatedMillis); } @Override public void recordConnectionTimeout() { connectionTimeoutCounterChild.inc(); } @Override public void close() { hikariCPCollector.remove(poolName); CONNECTION_TIMEOUT_COUNTER.remove(poolName); ELAPSED_ACQUIRED_HISTOGRAM.remove(poolName); ELAPSED_BORROWED_HISTOGRAM.remove(poolName); ELAPSED_CREATION_HISTOGRAM.remove(poolName); } } ================================================ FILE: src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusHistogramMetricsTrackerFactory.java ================================================ /* * Copyright (C) 2016 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics.prometheus; import com.zaxxer.hikari.metrics.IMetricsTracker; import com.zaxxer.hikari.metrics.MetricsTrackerFactory; import com.zaxxer.hikari.metrics.PoolStats; import com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory.RegistrationStatus; import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import static com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory.RegistrationStatus.*; /** *

{@code
 * HikariConfig config = new HikariConfig();
 * config.setMetricsTrackerFactory(new PrometheusHistogramMetricsTrackerFactory());
 * }
*/ public class PrometheusHistogramMetricsTrackerFactory implements MetricsTrackerFactory { private final static Map registrationStatuses = new ConcurrentHashMap<>(); private final HikariCPCollector collector = new HikariCPCollector(); private final CollectorRegistry collectorRegistry; /** * Default Constructor. The Hikari metrics are registered to the default * collector registry ({@code CollectorRegistry.defaultRegistry}). */ public PrometheusHistogramMetricsTrackerFactory() { this(CollectorRegistry.defaultRegistry); } /** * Constructor that allows to pass in a {@link CollectorRegistry} to which the * Hikari metrics are registered. * * @param collectorRegistry the collector registry to register the metrics to */ public PrometheusHistogramMetricsTrackerFactory(CollectorRegistry collectorRegistry) { this.collectorRegistry = collectorRegistry; } @Override public IMetricsTracker create(String poolName, PoolStats poolStats) { registerCollector(this.collector, this.collectorRegistry); this.collector.add(poolName, poolStats); return new PrometheusHistogramMetricsTracker(poolName, this.collectorRegistry, this.collector); } private void registerCollector(Collector collector, CollectorRegistry collectorRegistry) { if (registrationStatuses.putIfAbsent(collectorRegistry, REGISTERED) == null) { collector.register(collectorRegistry); } } } ================================================ FILE: src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTracker.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics.prometheus; import com.zaxxer.hikari.metrics.IMetricsTracker; import com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory.RegistrationStatus; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.Counter; import io.prometheus.client.Summary; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import static com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory.RegistrationStatus.REGISTERED; /** *
{@code
 * HikariConfig config = new HikariConfig();
 * config.setMetricsTrackerFactory(new PrometheusMetricsTracker());
 * }
* or *
{@code
 * config.setMetricsTrackerFactory(new PrometheusMetricsTracker(new CollectorRegistry()));
 * }
*/ class PrometheusMetricsTracker implements IMetricsTracker { private final static Counter CONNECTION_TIMEOUT_COUNTER = Counter.build() .name("hikaricp_connection_timeout_total") .labelNames("pool") .help("Connection timeout total count") .create(); private final static Summary ELAPSED_ACQUIRED_SUMMARY = createSummary("hikaricp_connection_acquired_nanos", "Connection acquired time (ns)"); private final static Summary ELAPSED_USAGE_SUMMARY = createSummary("hikaricp_connection_usage_millis", "Connection usage (ms)"); private final static Summary ELAPSED_CREATION_SUMMARY = createSummary("hikaricp_connection_creation_millis", "Connection creation (ms)"); private final static Map registrationStatuses = new ConcurrentHashMap<>(); private final String poolName; private final HikariCPCollector hikariCPCollector; private final Counter.Child connectionTimeoutCounterChild; private final Summary.Child elapsedAcquiredSummaryChild; private final Summary.Child elapsedUsageSummaryChild; private final Summary.Child elapsedCreationSummaryChild; PrometheusMetricsTracker(String poolName, CollectorRegistry collectorRegistry, HikariCPCollector hikariCPCollector) { registerMetrics(collectorRegistry); this.poolName = poolName; this.hikariCPCollector = hikariCPCollector; this.connectionTimeoutCounterChild = CONNECTION_TIMEOUT_COUNTER.labels(poolName); this.elapsedAcquiredSummaryChild = ELAPSED_ACQUIRED_SUMMARY.labels(poolName); this.elapsedUsageSummaryChild = ELAPSED_USAGE_SUMMARY.labels(poolName); this.elapsedCreationSummaryChild = ELAPSED_CREATION_SUMMARY.labels(poolName); } private void registerMetrics(CollectorRegistry collectorRegistry) { if (registrationStatuses.putIfAbsent(collectorRegistry, REGISTERED) == null) { CONNECTION_TIMEOUT_COUNTER.register(collectorRegistry); ELAPSED_ACQUIRED_SUMMARY.register(collectorRegistry); ELAPSED_USAGE_SUMMARY.register(collectorRegistry); ELAPSED_CREATION_SUMMARY.register(collectorRegistry); } } @Override public void recordConnectionAcquiredNanos(long elapsedAcquiredNanos) { elapsedAcquiredSummaryChild.observe(elapsedAcquiredNanos); } @Override public void recordConnectionUsageMillis(long elapsedBorrowedMillis) { elapsedUsageSummaryChild.observe(elapsedBorrowedMillis); } @Override public void recordConnectionCreatedMillis(long connectionCreatedMillis) { elapsedCreationSummaryChild.observe(connectionCreatedMillis); } @Override public void recordConnectionTimeout() { connectionTimeoutCounterChild.inc(); } private static Summary createSummary(String name, String help) { return Summary.build() .name(name) .labelNames("pool") .help(help) .quantile(0.5, 0.05) .quantile(0.95, 0.01) .quantile(0.99, 0.001) .maxAgeSeconds(TimeUnit.MINUTES.toSeconds(5)) .ageBuckets(5) .create(); } @Override public void close() { hikariCPCollector.remove(poolName); CONNECTION_TIMEOUT_COUNTER.remove(poolName); ELAPSED_ACQUIRED_SUMMARY.remove(poolName); ELAPSED_USAGE_SUMMARY.remove(poolName); ELAPSED_CREATION_SUMMARY.remove(poolName); } } ================================================ FILE: src/main/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactory.java ================================================ /* * Copyright (C) 2016 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics.prometheus; import com.zaxxer.hikari.metrics.IMetricsTracker; import com.zaxxer.hikari.metrics.MetricsTrackerFactory; import com.zaxxer.hikari.metrics.PoolStats; import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import static com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory.RegistrationStatus.REGISTERED; /** *
{@code
 * HikariConfig config = new HikariConfig();
 * config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
 * }
* or *
{@code
 * config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(new CollectorRegistry()));
 * }
* * Note: the internal {@link io.prometheus.client.Summary} requires heavy locks. Consider using * {@link PrometheusHistogramMetricsTrackerFactory} if performance plays a role and you don't need the summary per se. */ public class PrometheusMetricsTrackerFactory implements MetricsTrackerFactory { private final static Map registrationStatuses = new ConcurrentHashMap<>(); private final HikariCPCollector collector = new HikariCPCollector(); private final CollectorRegistry collectorRegistry; enum RegistrationStatus { REGISTERED } /** * Default Constructor. The Hikari metrics are registered to the default * collector registry ({@code CollectorRegistry.defaultRegistry}). */ public PrometheusMetricsTrackerFactory() { this(CollectorRegistry.defaultRegistry); } /** * Constructor that allows to pass in a {@link CollectorRegistry} to which the * Hikari metrics are registered. * * @param collectorRegistry the collector registry to register the metrics to */ public PrometheusMetricsTrackerFactory(CollectorRegistry collectorRegistry) { this.collectorRegistry = collectorRegistry; } @Override public IMetricsTracker create(String poolName, PoolStats poolStats) { registerCollector(this.collector, this.collectorRegistry); this.collector.add(poolName, poolStats); return new PrometheusMetricsTracker(poolName, this.collectorRegistry, this.collector); } private void registerCollector(Collector collector, CollectorRegistry collectorRegistry) { if (registrationStatuses.putIfAbsent(collectorRegistry, REGISTERED) == null) { collector.register(collectorRegistry); } } } ================================================ FILE: src/main/java/com/zaxxer/hikari/pool/HikariPool.java ================================================ /* * 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 com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariPoolMXBean; import com.zaxxer.hikari.metrics.MetricsTrackerFactory; import com.zaxxer.hikari.metrics.PoolStats; import com.zaxxer.hikari.util.ConcurrentBag; import com.zaxxer.hikari.util.ConcurrentBag.IBagStateListener; import com.zaxxer.hikari.util.SuspendResumeLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationTargetException; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLTransientConnectionException; import java.util.Optional; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import static com.zaxxer.hikari.util.ClockSource.*; import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_IN_USE; import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_NOT_IN_USE; import static com.zaxxer.hikari.util.UtilityElf.*; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; /** * This is the primary connection pool class that provides the basic * pooling behavior for HikariCP. * * @author Brett Wooldridge * @hidden */ public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateListener { private final Logger logger = LoggerFactory.getLogger(HikariPool.class); public static final int POOL_NORMAL = 0; public static final int POOL_SUSPENDED = 1; public static final int POOL_SHUTDOWN = 2; public volatile int poolState; private final long aliveBypassWindowMs = Long.getLong("com.zaxxer.hikari.aliveBypassWindowMs", MILLISECONDS.toMillis(500)); private final long housekeepingPeriodMs = Long.getLong("com.zaxxer.hikari.housekeeping.periodMs", SECONDS.toMillis(30)); private final long lifeTimeVarianceFactor = Math.min(40, Math.max(2, Long.getLong("com.zaxxer.hikari.lifeTimeVarianceFactor", 4))); // variance% = 100 / factor private final boolean isRequestBoundariesEnabled = Boolean.getBoolean("com.zaxxer.hikari.enableRequestBoundaries"); private static final String EVICTED_CONNECTION_MESSAGE = "(connection was evicted)"; private static final String DEAD_CONNECTION_MESSAGE = "(connection is dead)"; private final PoolEntryCreator poolEntryCreator = new PoolEntryCreator(); private final PoolEntryCreator postFillPoolEntryCreator = new PoolEntryCreator("After adding "); private final ThreadPoolExecutor addConnectionExecutor; private final ThreadPoolExecutor closeConnectionExecutor; private final ConcurrentBag connectionBag; private final ProxyLeakTaskFactory leakTaskFactory; private final SuspendResumeLock suspendResumeLock; private final ScheduledExecutorService houseKeepingExecutorService; private ScheduledFuture houseKeeperTask; /** * Construct a HikariPool with the specified configuration. * * @param config a HikariConfig instance */ public HikariPool(final HikariConfig config) { super(config); this.connectionBag = new ConcurrentBag<>(this); this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK; this.houseKeepingExecutorService = initializeHouseKeepingExecutorService(); checkFailFast(); if (config.getMetricsTrackerFactory() != null) { setMetricsTrackerFactory(config.getMetricsTrackerFactory()); } else { setMetricRegistry(config.getMetricRegistry()); } setHealthCheckRegistry(config.getHealthCheckRegistry()); handleMBeans(this, true); ThreadFactory threadFactory = config.getThreadFactory(); final int maxPoolSize = config.getMaximumPoolSize(); this.addConnectionExecutor = createThreadPoolExecutor(maxPoolSize, poolName + ":connection-adder", threadFactory, new CustomDiscardPolicy()); this.closeConnectionExecutor = createThreadPoolExecutor(maxPoolSize, poolName + ":connection-closer", threadFactory, new ThreadPoolExecutor.CallerRunsPolicy()); this.leakTaskFactory = new ProxyLeakTaskFactory(config.getLeakDetectionThreshold(), houseKeepingExecutorService); this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, housekeepingPeriodMs, MILLISECONDS); if (Boolean.getBoolean("com.zaxxer.hikari.blockUntilFilled") && config.getInitializationFailTimeout() > 1) { addConnectionExecutor.setMaximumPoolSize(Math.min(16, Runtime.getRuntime().availableProcessors())); addConnectionExecutor.setCorePoolSize(Math.min(16, Runtime.getRuntime().availableProcessors())); final long startTime = currentTime(); while (elapsedMillis(startTime) < config.getInitializationFailTimeout() && getTotalConnections() < config.getMinimumIdle()) { quietlySleep(MILLISECONDS.toMillis(100)); } addConnectionExecutor.setCorePoolSize(1); addConnectionExecutor.setMaximumPoolSize(1); } } /** * Get a connection from the pool, or timeout after connectionTimeout milliseconds. * * @return a java.sql.Connection instance * @throws SQLException thrown if a timeout occurs trying to obtain a connection */ public Connection getConnection() throws SQLException { return getConnection(connectionTimeout); } /** * Get a connection from the pool, or timeout after the specified number of milliseconds. * * @param hardTimeout the maximum time to wait for a connection from the pool * @return a java.sql.Connection instance * @throws SQLException thrown if a timeout occurs trying to obtain a connection */ public Connection getConnection(final long hardTimeout) throws SQLException { suspendResumeLock.acquire(); final var startTime = currentTime(); try { var timeout = hardTimeout; do { var poolEntry = connectionBag.borrow(timeout, MILLISECONDS); if (poolEntry == null) { break; // We timed out... break and throw exception } final var now = currentTime(); if (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > aliveBypassWindowMs && isConnectionDead(poolEntry.connection))) { closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE); timeout = hardTimeout - elapsedMillis(startTime); } else { metricsTracker.recordBorrowStats(poolEntry, startTime); if (isRequestBoundariesEnabled) { try { poolEntry.connection.beginRequest(); } catch (SQLException e) { logger.warn("beginRequest Failed for: {}, ({})", poolEntry.connection, e.getMessage()); } } return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry)); } } while (timeout > 0L); metricsTracker.recordBorrowTimeoutStats(startTime); throw createTimeoutException(startTime); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new SQLException(poolName + " - Interrupted during connection acquisition", e); } finally { suspendResumeLock.release(); } } /** * Shutdown the pool, closing all idle connections and aborting or closing * active connections. * * @throws InterruptedException thrown if the thread is interrupted during shutdown */ public synchronized void shutdown() throws InterruptedException { try { poolState = POOL_SHUTDOWN; if (addConnectionExecutor == null) { // pool never started return; } logPoolState("Before shutdown "); if (houseKeeperTask != null) { houseKeeperTask.cancel(false); houseKeeperTask = null; } softEvictConnections(); addConnectionExecutor.shutdown(); if (!addConnectionExecutor.awaitTermination(getLoginTimeout(), SECONDS)) { logger.warn("Timed-out waiting for add connection executor to shutdown"); } destroyHouseKeepingExecutorService(); connectionBag.close(); final var assassinExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), poolName + ":connection-assassinator", config.getThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); try { final var start = currentTime(); do { abortActiveConnections(assassinExecutor); softEvictConnections(); } while (getTotalConnections() > 0 && elapsedMillis(start) < SECONDS.toMillis(10)); } finally { assassinExecutor.shutdown(); if (!assassinExecutor.awaitTermination(10L, SECONDS)) { logger.warn("Timed-out waiting for connection assassin to shutdown"); } } shutdownNetworkTimeoutExecutor(); closeConnectionExecutor.shutdown(); if (!closeConnectionExecutor.awaitTermination(10L, SECONDS)) { logger.warn("Timed-out waiting for close connection executor to shutdown"); } } finally { logPoolState("After shutdown "); handleMBeans(this, false); metricsTracker.close(); } } /** * Evict a Connection from the pool. * * @param connection the Connection to evict (actually a {@link ProxyConnection}) */ public void evictConnection(Connection connection) { var proxyConnection = (ProxyConnection) connection; proxyConnection.cancelLeakTask(); try { softEvictConnection(proxyConnection.getPoolEntry(), "(connection evicted by user)", !connection.isClosed() /* owner */); } catch (SQLException e) { // unreachable in HikariCP, but we're still forced to catch it } } /** * Set a metrics registry to be used when registering metrics collectors. The HikariDataSource prevents this * method from being called more than once. * * @param metricRegistry the metrics registry instance to use */ public void setMetricRegistry(Object metricRegistry) { if (metricRegistry != null && safeIsAssignableFrom(metricRegistry, "com.codahale.metrics.MetricRegistry")) { setMetricsTrackerFactory(createInstance("com.zaxxer.hikari.metrics.dropwizard.CodahaleMetricsTrackerFactory", MetricsTrackerFactory.class, metricRegistry)); } else if (metricRegistry != null && safeIsAssignableFrom(metricRegistry, "io.dropwizard.metrics5.MetricRegistry")) { setMetricsTrackerFactory(createInstance("com.zaxxer.hikari.metrics.dropwizard.Dropwizard5MetricsTrackerFactory", MetricsTrackerFactory.class, metricRegistry)); } else if (metricRegistry != null && safeIsAssignableFrom(metricRegistry, "io.micrometer.core.instrument.MeterRegistry")) { setMetricsTrackerFactory(createInstance("com.zaxxer.hikari.metrics.micrometer.MicrometerMetricsTrackerFactory", MetricsTrackerFactory.class, metricRegistry)); } else { setMetricsTrackerFactory(null); } } /** * Set the MetricsTrackerFactory to be used to create the IMetricsTracker instance used by the pool. * * @param metricsTrackerFactory an instance of a class that subclasses MetricsTrackerFactory */ public void setMetricsTrackerFactory(MetricsTrackerFactory metricsTrackerFactory) { if (metricsTrackerFactory != null) { this.metricsTracker = new MetricsTrackerDelegate(metricsTrackerFactory.create(config.getPoolName(), getPoolStats())); } else { this.metricsTracker = new NopMetricsTrackerDelegate(); } } /** * Set the health check registry to be used when registering health checks. Currently only Codahale health * checks are supported. * * @param healthCheckRegistry the health check registry instance to use */ public void setHealthCheckRegistry(Object healthCheckRegistry) { if (healthCheckRegistry != null) { try { Class checkerClazz = HikariPool.class.getClassLoader().loadClass("com.zaxxer.hikari.metrics.dropwizard.CodahaleHealthChecker"); Class healthCheckRegistryClazz = HikariPool.class.getClassLoader().loadClass("com.codahale.metrics.health.HealthCheckRegistry"); checkerClazz.getMethod("registerHealthChecks", HikariPool.class, HikariConfig.class, healthCheckRegistryClazz) .invoke(null, this, config, healthCheckRegistry); } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } } // *********************************************************************** // IBagStateListener callback // *********************************************************************** /** {@inheritDoc} */ @Override public void addBagItem(final int waiting) { if (waiting > addConnectionExecutor.getQueue().size()) addConnectionExecutor.submit(poolEntryCreator); } // *********************************************************************** // HikariPoolMBean methods // *********************************************************************** /** {@inheritDoc} */ @Override public int getActiveConnections() { return connectionBag.getCount(STATE_IN_USE); } /** {@inheritDoc} */ @Override public int getIdleConnections() { return connectionBag.getCount(STATE_NOT_IN_USE); } /** {@inheritDoc} */ @Override public int getTotalConnections() { return connectionBag.size(); } /** {@inheritDoc} */ @Override public int getThreadsAwaitingConnection() { return connectionBag.getWaitingThreadCount(); } /** {@inheritDoc} */ @Override public void softEvictConnections() { connectionBag.values().forEach(poolEntry -> softEvictConnection(poolEntry, "(connection evicted)", false /* not owner */)); } /** {@inheritDoc} */ @Override public synchronized void suspendPool() { if (suspendResumeLock == SuspendResumeLock.FAUX_LOCK) { throw new IllegalStateException(poolName + " - is not suspendable"); } else if (poolState != POOL_SUSPENDED) { suspendResumeLock.suspend(); poolState = POOL_SUSPENDED; } } /** {@inheritDoc} */ @Override public synchronized void resumePool() { if (poolState == POOL_SUSPENDED) { poolState = POOL_NORMAL; fillPool(false); suspendResumeLock.resume(); } } // *********************************************************************** // Package methods // *********************************************************************** /** * Log the current pool state at debug level. * * @param prefix an optional prefix to prepend the log message */ void logPoolState(String... prefix) { if (logger.isDebugEnabled()) { logger.debug("{} - {}stats (total={}/{}, idle={}/{}, active={}, waiting={})", poolName, (prefix.length > 0 ? prefix[0] : ""), getTotalConnections(), config.getMaximumPoolSize(), getIdleConnections(), config.getMinimumIdle(), getActiveConnections(), getThreadsAwaitingConnection()); } } /** * Recycle PoolEntry (add back to the pool) * * @param poolEntry the PoolEntry to recycle */ @Override void recycle(final PoolEntry poolEntry) { metricsTracker.recordConnectionUsage(poolEntry); if (poolEntry.isMarkedEvicted()) { closeConnection(poolEntry, EVICTED_CONNECTION_MESSAGE); } else { if (isRequestBoundariesEnabled) { try { poolEntry.connection.endRequest(); } catch (SQLException e) { logger.warn("endRequest Failed for: {},({})", poolEntry.connection, e.getMessage()); } } connectionBag.requite(poolEntry); } } /** * Permanently close the real (underlying) connection (eat any exception). * * @param poolEntry poolEntry having the connection to close * @param closureReason reason to close */ void closeConnection(final PoolEntry poolEntry, final String closureReason) { if (connectionBag.remove(poolEntry)) { final var connection = poolEntry.close(); closeConnectionExecutor.execute(() -> { quietlyCloseConnection(connection, closureReason); if (poolState == POOL_NORMAL) { fillPool(false); } }); } } @SuppressWarnings("unused") int[] getPoolStateCounts() { return connectionBag.getStateCounts(); } // *********************************************************************** // Private methods // *********************************************************************** /** * Creating new poolEntry. If maxLifetime is configured, create a future End-of-life task with variance from * the maxLifetime time to ensure there is no massive die-off of Connections in the pool. */ private PoolEntry createPoolEntry() { try { final var poolEntry = newPoolEntry(getTotalConnections() == 0); final var maxLifetime = config.getMaxLifetime(); if (maxLifetime > 0) { // default variance upto 25% of the maxLifetime (random) final var variance = maxLifetime > 10_000L ? ThreadLocalRandom.current().nextLong( maxLifetime / lifeTimeVarianceFactor ) : 0L; final var lifetime = maxLifetime - variance; poolEntry.setFutureEol(houseKeepingExecutorService.schedule(new MaxLifetimeTask(poolEntry), lifetime, MILLISECONDS)); } final long keepaliveTime = config.getKeepaliveTime(); if (keepaliveTime > 0) { // variance up to 20% of the heartbeat time final var variance = ThreadLocalRandom.current().nextLong(keepaliveTime / 5); final var heartbeatTime = keepaliveTime - variance; poolEntry.setKeepalive(houseKeepingExecutorService.scheduleWithFixedDelay(new KeepaliveTask(poolEntry), heartbeatTime, heartbeatTime, MILLISECONDS)); } return poolEntry; } catch (ConnectionSetupException e) { if (poolState == POOL_NORMAL) { // we check POOL_NORMAL to avoid a flood of messages if shutdown() is running concurrently logger.debug("{} - Error thrown while acquiring connection from data source", poolName, e.getCause()); } } catch (Throwable e) { if (poolState == POOL_NORMAL) { // we check POOL_NORMAL to avoid a flood of messages if shutdown() is running concurrently logger.debug("{} - Cannot acquire connection from data source", poolName, e); } } return null; } /** * Fill pool up from current idle connections (as they are perceived at the point of execution) to minimumIdle connections. */ private synchronized void fillPool(final boolean isAfterAdd) { final var idle = getIdleConnections(); final var shouldAdd = getTotalConnections() < config.getMaximumPoolSize() && idle < config.getMinimumIdle(); if (shouldAdd) { final var countToAdd = config.getMinimumIdle() - idle; for (int i = 0; i < countToAdd; i++) addConnectionExecutor.submit(isAfterAdd ? postFillPoolEntryCreator : poolEntryCreator); } else if (isAfterAdd) { logger.debug("{} - Fill pool skipped, pool has sufficient level or currently being filled.", poolName); } } /** * Attempt to abort or close active connections. * * @param assassinExecutor the ExecutorService to pass to Connection.abort() */ private void abortActiveConnections(final ExecutorService assassinExecutor) { for (var poolEntry : connectionBag.values(STATE_IN_USE)) { Connection connection = poolEntry.close(); try { connection.abort(assassinExecutor); } catch (Throwable e) { quietlyCloseConnection(connection, "(connection aborted during shutdown)"); } finally { connectionBag.remove(poolEntry); } } } /** * If {@code initializationFailTimeout} is configured, check that we have DB connectivity. * * @throws PoolInitializationException if fails to create or validate connection * @see HikariConfig#setInitializationFailTimeout(long) */ private void checkFailFast() { final var initializationFailTimeout = config.getInitializationFailTimeout(); if (initializationFailTimeout < 0) { return; } final var startTime = currentTime(); do { final var poolEntry = createPoolEntry(); if (poolEntry != null) { if (config.getMinimumIdle() > 0) { connectionBag.add(poolEntry); logger.info("{} - Added connection {}", poolName, poolEntry.connection); } else { quietlyCloseConnection(poolEntry.close(), "(initialization check complete and minimumIdle is zero)"); } return; } if (getLastConnectionFailure() instanceof ConnectionSetupException) { throwPoolInitializationException(getLastConnectionFailure().getCause()); } quietlySleep(SECONDS.toMillis(1)); } while (elapsedMillis(startTime) < initializationFailTimeout); if (initializationFailTimeout > 0) { throwPoolInitializationException(getLastConnectionFailure()); } } /** * Log the Throwable that caused pool initialization to fail, and then throw a PoolInitializationException with * that cause attached. * * @param t the Throwable that caused the pool to fail to initialize (possibly null) */ private void throwPoolInitializationException(Throwable t) { destroyHouseKeepingExecutorService(); throw new PoolInitializationException(t); } /** * "Soft" evict a Connection (/PoolEntry) from the pool. If this method is being called by the user directly * through {@link HikariDataSource#evictConnection(Connection)} then {@code owner} is {@code true}. *

* If the caller is the owner, or if the Connection is idle (i.e. can be "reserved" in the {@link ConcurrentBag}), * then we can close the connection immediately. Otherwise, we leave it "marked" for eviction so that it is evicted * the next time someone tries to acquire it from the pool. *

* @param poolEntry the PoolEntry (/Connection) to "soft" evict from the pool * @param reason the reason that the connection is being evicted * @param owner true if the caller is the owner of the connection, false otherwise * @return true if the connection was evicted (closed), false if it was merely marked for eviction */ private boolean softEvictConnection(final PoolEntry poolEntry, final String reason, final boolean owner) { poolEntry.markEvicted(); if (owner || connectionBag.reserve(poolEntry)) { closeConnection(poolEntry, reason); return true; } return false; } /** * Create/initialize the Housekeeping service {@link ScheduledExecutorService}. If the user specified an Executor * to be used in the {@link HikariConfig}, then we use that. If no Executor was specified (typical), then create * an Executor and configure it. * * @return either the user specified {@link ScheduledExecutorService}, or the one we created */ private ScheduledExecutorService initializeHouseKeepingExecutorService() { if (config.getScheduledExecutor() == null) { final var threadFactory = Optional.ofNullable(config.getThreadFactory()).orElseGet(() -> new DefaultThreadFactory(poolName + ":housekeeper")); final var executor = new ScheduledThreadPoolExecutor(1, threadFactory, new ThreadPoolExecutor.DiscardPolicy()); executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); executor.setRemoveOnCancelPolicy(true); return executor; } else { return config.getScheduledExecutor(); } } /** * Destroy (/shutdown) the Housekeeping service Executor, if it was the one that we created. */ private void destroyHouseKeepingExecutorService() { if (config.getScheduledExecutor() == null) { houseKeepingExecutorService.shutdownNow(); } } /** * Create a PoolStats instance that will be used by metrics tracking, with a pollable resolution of 1 second. * * @return a PoolStats instance */ private PoolStats getPoolStats() { return new PoolStats(SECONDS.toMillis(1)) { @Override protected void update() { this.pendingThreads = HikariPool.this.getThreadsAwaitingConnection(); this.idleConnections = HikariPool.this.getIdleConnections(); this.totalConnections = HikariPool.this.getTotalConnections(); this.activeConnections = HikariPool.this.getActiveConnections(); this.maxConnections = config.getMaximumPoolSize(); this.minConnections = config.getMinimumIdle(); } }; } /** * Create a timeout exception (specifically, {@link SQLTransientConnectionException}) to be thrown, because a * timeout occurred when trying to acquire a Connection from the pool. If there was an underlying cause for the * timeout, e.g. a SQLException thrown by the driver while trying to create a new Connection, then use the * SQL State from that exception as our own and additionally set that exception as the "next" SQLException inside * our exception. *

* As a side effect, log the timeout failure at DEBUG, and record the timeout failure in the metrics tracker. * * @param startTime the start time (timestamp) of the acquisition attempt * @return a SQLException to be thrown from {@link #getConnection()} */ private SQLException createTimeoutException(long startTime) { logPoolState("Timeout failure "); metricsTracker.recordConnectionTimeout(); String sqlState = null; int errorCode = 0; final var originalException = getLastConnectionFailure(); if (originalException instanceof SQLException) { sqlState = ((SQLException) originalException).getSQLState(); errorCode = ((SQLException) originalException).getErrorCode(); } final var connectionException = new SQLTransientConnectionException( poolName + " - Connection is not available, request timed out after " + elapsedMillis(startTime) + "ms " + "(total=" + getTotalConnections() + ", active=" + getActiveConnections() + ", idle=" + getIdleConnections() + ", waiting=" + getThreadsAwaitingConnection() + ")", sqlState, errorCode, originalException); if (originalException instanceof SQLException) { connectionException.setNextException((SQLException) originalException); } return connectionException; } // *********************************************************************** // Non-anonymous Inner-classes // *********************************************************************** /** * Creating and adding poolEntries (connections) to the pool. */ private final class PoolEntryCreator implements Callable { private final String loggingPrefix; PoolEntryCreator() { this(null); } PoolEntryCreator(String loggingPrefix) { this.loggingPrefix = loggingPrefix; } @Override public Boolean call() { var backoffMs = 10L; var added = false; try { while (shouldContinueCreating()) { final var poolEntry = createPoolEntry(); if (poolEntry != null) { added = true; connectionBag.add(poolEntry); logger.debug("{} - Added connection {}", poolName, poolEntry.connection); quietlySleep(30L); break; } else { // failed to get connection from db, sleep and retry if (loggingPrefix != null && backoffMs % 50 == 0) logger.debug("{} - Connection add failed, sleeping with backoff: {}ms", poolName, backoffMs); quietlySleep(backoffMs); backoffMs = Math.min(SECONDS.toMillis(5), backoffMs * 2); } } } finally { if (added && loggingPrefix != null) logPoolState(loggingPrefix); else if (!added) logPoolState("Connection not added, "); } // Pool is suspended, shutdown, or at max size return Boolean.FALSE; } /** * We only create connections if we need another idle connection or have threads still waiting * for a new connection. Otherwise, we bail out of the request to create. * * @return true if we should create a connection, false if the need has disappeared */ private synchronized boolean shouldContinueCreating() { return poolState == POOL_NORMAL && !Thread.interrupted() && getTotalConnections() < config.getMaximumPoolSize() && (getIdleConnections() < config.getMinimumIdle() || connectionBag.getWaitingThreadCount() > getIdleConnections()); } } /** * The housekeeping task to retire and maintain minimum idle connections. */ private final class HouseKeeper implements Runnable { private volatile long previous = plusMillis(currentTime(), -housekeepingPeriodMs); @SuppressWarnings("AtomicFieldUpdaterNotStaticFinal") private final AtomicReferenceFieldUpdater catalogUpdater = AtomicReferenceFieldUpdater.newUpdater(PoolBase.class, String.class, "catalog"); @Override public void run() { try { // refresh values in case they changed via MBean connectionTimeout = config.getConnectionTimeout(); validationTimeout = config.getValidationTimeout(); leakTaskFactory.updateLeakDetectionThreshold(config.getLeakDetectionThreshold()); if (config.getCatalog() != null && !config.getCatalog().equals(catalog)) { catalogUpdater.set(HikariPool.this, config.getCatalog()); } final var idleTimeout = config.getIdleTimeout(); final var now = currentTime(); // Detect retrograde time, allowing +128ms as per NTP spec. if (plusMillis(now, 128) < plusMillis(previous, housekeepingPeriodMs)) { logger.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.", poolName, elapsedDisplayString(previous, now)); previous = now; softEvictConnections(); return; } else if (now > plusMillis(previous, (3 * housekeepingPeriodMs) / 2)) { // No point evicting for forward clock motion, this merely accelerates connection retirement anyway logger.warn("{} - Thread starvation or clock leap detected (housekeeper delta={}).", poolName, elapsedDisplayString(previous, now)); } previous = now; if (idleTimeout > 0L && config.getMinimumIdle() < config.getMaximumPoolSize()) { logPoolState("Before cleanup "); final var notInUse = connectionBag.values(STATE_NOT_IN_USE); var maxToRemove = notInUse.size() - config.getMinimumIdle(); for (PoolEntry entry : notInUse) { if (maxToRemove > 0 && elapsedMillis(entry.lastAccessed, now) > idleTimeout && connectionBag.reserve(entry)) { closeConnection(entry, "(connection has passed idleTimeout)"); maxToRemove--; } } logPoolState("After cleanup "); } else logPoolState("Pool "); fillPool(true); // Try to maintain minimum connections } catch (Exception e) { logger.error("Unexpected exception in housekeeping task", e); } } } private final class MaxLifetimeTask implements Runnable { private final PoolEntry poolEntry; MaxLifetimeTask(final PoolEntry poolEntry) { this.poolEntry = poolEntry; } public void run() { if (softEvictConnection(poolEntry, "(connection has passed maxLifetime)", false /* not owner */)) { addBagItem(connectionBag.getWaitingThreadCount()); } } } private final class KeepaliveTask implements Runnable { private final PoolEntry poolEntry; KeepaliveTask(final PoolEntry poolEntry) { this.poolEntry = poolEntry; } public void run() { if (connectionBag.reserve(poolEntry)) { if (isConnectionDead(poolEntry.connection)) { softEvictConnection(poolEntry, DEAD_CONNECTION_MESSAGE, true); addBagItem(connectionBag.getWaitingThreadCount()); } else { connectionBag.unreserve(poolEntry); logger.debug("{} - keepalive: connection {} is alive", poolName, poolEntry.connection); } } } } /** * Exception thrown when the pool fails to initialize, typically due to a failure to create or validate a connection. * @hidden */ public static class PoolInitializationException extends RuntimeException { private static final long serialVersionUID = 929872118275916520L; /** * Construct an exception, possibly wrapping the provided Throwable as the cause. * @param t the Throwable to wrap */ public PoolInitializationException(Throwable t) { super("Failed to initialize pool: " + t.getMessage(), t); } } } ================================================ FILE: src/main/java/com/zaxxer/hikari/pool/PoolBase.java ================================================ /* * 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 com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariCredentialsProvider; import com.zaxxer.hikari.SQLExceptionOverride; import com.zaxxer.hikari.metrics.IMetricsTracker; import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException; import com.zaxxer.hikari.util.Credentials; import com.zaxxer.hikari.util.DriverDataSource; import com.zaxxer.hikari.util.PropertyElf; import com.zaxxer.hikari.util.UtilityElf; import com.zaxxer.hikari.util.UtilityElf.DefaultThreadFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.management.ObjectName; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.sql.DataSource; import java.lang.management.ManagementFactory; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLTransientConnectionException; import java.sql.Statement; import java.util.StringJoiner; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import static com.zaxxer.hikari.pool.ProxyConnection.*; import static com.zaxxer.hikari.util.ClockSource.*; import static com.zaxxer.hikari.util.UtilityElf.createInstance; import static java.util.concurrent.TimeUnit.*; abstract class PoolBase { private final Logger logger = LoggerFactory.getLogger(PoolBase.class); public final HikariConfig config; IMetricsTrackerDelegate metricsTracker; protected final String poolName; volatile String catalog; final AtomicReference lastConnectionFailure; final AtomicLong connectionFailureTimestamp; long connectionTimeout; long validationTimeout; SQLExceptionOverride exceptionOverride; HikariCredentialsProvider credentialsProvider; 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; private static final int MINIMUM_LOGIN_TIMEOUT = Integer.getInteger("com.zaxxer.hikari.minimumLoginTimeoutSecs", 1); private static final boolean LEGACY_USERPASS_DS_OVERRIDE = Boolean.getBoolean("com.zaxxer.hikari.legacy.supportUserPassDataSourceOverride"); private int networkTimeout; private volatile int isNetworkTimeoutSupported; private int isQueryTimeoutSupported; private int defaultTransactionIsolation; private int transactionIsolation; private Executor netTimeoutExecutor; private DataSource dataSource; private final String schema; private final boolean isReadOnly; private final boolean isAutoCommit; private final boolean isUseJdbc4Validation; private final boolean isIsolateInternalQueries; private volatile boolean isValidChecked; PoolBase(final HikariConfig config) { this.config = config; this.networkTimeout = UNINITIALIZED; this.catalog = config.getCatalog(); this.schema = config.getSchema(); this.isReadOnly = config.isReadOnly(); this.isAutoCommit = config.isAutoCommit(); this.exceptionOverride = config.getExceptionOverride(); this.credentialsProvider = config.getCredentialsProvider(); this.transactionIsolation = UtilityElf.getTransactionIsolation(config.getTransactionIsolation()); this.isQueryTimeoutSupported = UNINITIALIZED; this.isNetworkTimeoutSupported = UNINITIALIZED; this.isUseJdbc4Validation = config.getConnectionTestQuery() == null; this.isIsolateInternalQueries = config.isIsolateInternalQueries(); this.poolName = config.getPoolName(); this.connectionTimeout = config.getConnectionTimeout(); this.validationTimeout = config.getValidationTimeout(); this.lastConnectionFailure = new AtomicReference<>(); this.connectionFailureTimestamp = new AtomicLong(); initializeDataSource(); } /** {@inheritDoc} */ @Override public String toString() { return poolName; } abstract void recycle(final PoolEntry poolEntry); // *********************************************************************** // JDBC methods // *********************************************************************** void quietlyCloseConnection(final Connection connection, final String closureReason) { if (connection != null) { try { logger.debug("{} - Closing connection {}: {}", poolName, connection, closureReason); // continue with the close even if setNetworkTimeout() throws try (connection) { if (!connection.isClosed()) setNetworkTimeout(connection, SECONDS.toMillis(15)); } catch (SQLException e) { // ignore } } catch (Exception e) { logger.debug("{} - Closing connection {} failed", poolName, connection, e); } } } boolean isConnectionDead(final Connection connection) { try { setNetworkTimeout(connection, validationTimeout); try { final var validationSeconds = (int) Math.max(1000L, validationTimeout) / 1000; if (isUseJdbc4Validation) { return !connection.isValid(validationSeconds); } try (var statement = connection.createStatement()) { if (isNetworkTimeoutSupported != TRUE) { setQueryTimeout(statement, validationSeconds); } statement.execute(config.getConnectionTestQuery()); } } finally { setNetworkTimeout(connection, networkTimeout); if (isIsolateInternalQueries && !isAutoCommit) { connection.rollback(); } } return false; } catch (Exception e) { lastConnectionFailure.set(e); logger.warn("{} - Failed to validate connection {} ({}). Possibly consider using a shorter maxLifetime value.", poolName, connection, e.getMessage()); return true; } } Throwable getLastConnectionFailure() { return lastConnectionFailure.get(); } public DataSource getUnwrappedDataSource() { return dataSource; } // *********************************************************************** // PoolEntry methods // *********************************************************************** PoolEntry newPoolEntry(final boolean isEmptyPool) throws Exception { return new PoolEntry(newConnection(isEmptyPool), this, isReadOnly, isAutoCommit); } void resetConnectionState(final Connection connection, final ProxyConnection proxyConnection, final int dirtyBits) throws SQLException { int resetBits = 0; if ((dirtyBits & DIRTY_BIT_READONLY) != 0 && proxyConnection.getReadOnlyState() != isReadOnly) { connection.setReadOnly(isReadOnly); resetBits |= DIRTY_BIT_READONLY; } if ((dirtyBits & DIRTY_BIT_AUTOCOMMIT) != 0 && proxyConnection.getAutoCommitState() != isAutoCommit) { connection.setAutoCommit(isAutoCommit); resetBits |= DIRTY_BIT_AUTOCOMMIT; } if ((dirtyBits & DIRTY_BIT_ISOLATION) != 0 && proxyConnection.getTransactionIsolationState() != transactionIsolation) { //noinspection MagicConstant connection.setTransactionIsolation(transactionIsolation); resetBits |= DIRTY_BIT_ISOLATION; } if ((dirtyBits & DIRTY_BIT_CATALOG) != 0 && catalog != null && !catalog.equals(proxyConnection.getCatalogState())) { connection.setCatalog(catalog); resetBits |= DIRTY_BIT_CATALOG; } if ((dirtyBits & DIRTY_BIT_NETTIMEOUT) != 0 && proxyConnection.getNetworkTimeoutState() != networkTimeout) { setNetworkTimeout(connection, networkTimeout); resetBits |= DIRTY_BIT_NETTIMEOUT; } if ((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); } } void shutdownNetworkTimeoutExecutor() { isNetworkTimeoutSupported = UNINITIALIZED; if (netTimeoutExecutor instanceof ThreadPoolExecutor) { ((ThreadPoolExecutor) netTimeoutExecutor).shutdownNow(); } } long getLoginTimeout() { try { return (dataSource != null) ? dataSource.getLoginTimeout() : SECONDS.toSeconds(5); } catch (SQLException e) { return SECONDS.toSeconds(5); } } // *********************************************************************** // JMX methods // *********************************************************************** /** * Register MBeans for HikariConfig and HikariPool. * * @param hikariPool a HikariPool instance */ void handleMBeans(final HikariPool hikariPool, final boolean register) { if (!config.isRegisterMbeans()) { return; } try { final var mBeanServer = ManagementFactory.getPlatformMBeanServer(); ObjectName beanConfigName, beanPoolName; if ("true".equals(System.getProperty("hikaricp.jmx.register2.0"))) { beanConfigName = new ObjectName("com.zaxxer.hikari:type=PoolConfig,name=" + poolName); beanPoolName = new ObjectName("com.zaxxer.hikari:type=Pool,name=" + poolName); } else { beanConfigName = new ObjectName("com.zaxxer.hikari:type=PoolConfig (" + poolName + ")"); beanPoolName = new ObjectName("com.zaxxer.hikari:type=Pool (" + poolName + ")"); } if (register) { if (!mBeanServer.isRegistered(beanConfigName)) { mBeanServer.registerMBean(config, beanConfigName); mBeanServer.registerMBean(hikariPool, beanPoolName); } else { logger.error("{} - JMX name ({}) is already registered.", poolName, poolName); } } else if (mBeanServer.isRegistered(beanConfigName)) { mBeanServer.unregisterMBean(beanConfigName); mBeanServer.unregisterMBean(beanPoolName); } } catch (Exception e) { logger.warn("{} - Failed to {} management beans.", poolName, (register ? "register" : "unregister"), e); } } // *********************************************************************** // Private methods // *********************************************************************** /** * Create/initialize the underlying DataSource. */ private void initializeDataSource() { final var jdbcUrl = config.getJdbcUrl(); final var dsClassName = config.getDataSourceClassName(); final var driverClassName = config.getDriverClassName(); final var dataSourceJNDI = config.getDataSourceJNDI(); final var dataSourceProperties = config.getDataSourceProperties(); final var credentials = getCredentials(); var ds = config.getDataSource(); if (dsClassName != null && ds == null) { ds = createInstance(dsClassName, DataSource.class); PropertyElf.setTargetFromProperties(ds, dataSourceProperties); } else if (jdbcUrl != null && ds == null) { ds = new DriverDataSource(jdbcUrl, driverClassName, dataSourceProperties, credentials.getUsername(), credentials.getPassword()); } else if (dataSourceJNDI != null && ds == null) { try { var ic = new InitialContext(); ds = (DataSource) ic.lookup(dataSourceJNDI); } catch (NamingException e) { throw new PoolInitializationException(e); } } if (ds != null) { setLoginTimeout(ds); createNetworkTimeoutExecutor(ds, dsClassName, jdbcUrl); } this.dataSource = ds; } /** * Obtain connection from data source. * * @return a Connection */ private Connection newConnection(final boolean isEmptyPool) throws Exception { final var start = currentTime(); final var id = java.util.UUID.randomUUID(); Connection connection = null; try { final var credentials = getCredentials(); final var username = credentials.getUsername(); final var password = credentials.getPassword(); logger.debug("{} - Attempting to create/setup new connection ({})", poolName, id); connection = (username == null) ? dataSource.getConnection() : dataSource.getConnection(username, password); if (connection == null) { throw new SQLTransientConnectionException("DataSource returned null unexpectedly"); } setupConnection(connection); lastConnectionFailure.set(null); connectionFailureTimestamp.set(0); logger.debug("{} - Established new connection ({})", poolName, id); return connection; } catch (Throwable t) { logger.debug("{} - Failed to create/setup connection ({}): {}", poolName, id, t.getMessage()); connectionFailureTimestamp.compareAndSet(0, start); if (isEmptyPool && elapsedMillis(connectionFailureTimestamp.get()) > MINUTES.toMillis(1)) { logger.warn("{} - Pool is empty, failed to create/setup connection ({})", poolName, id, t); connectionFailureTimestamp.set(0); } if (connection != null) { quietlyCloseConnection(connection, "(Failed to create/setup connection (".concat(id.toString()).concat(")")); } lastConnectionFailure.set(t); throw t; } finally { // tracker will be null during failFast check if (metricsTracker != null) { metricsTracker.recordConnectionCreated(elapsedMillis(start)); } } } /** * Set up a connection initial state. * * @param connection a Connection * @throws ConnectionSetupException thrown if any exception is encountered */ private void setupConnection(final Connection connection) throws ConnectionSetupException { try { if (networkTimeout == UNINITIALIZED) { networkTimeout = getAndSetNetworkTimeout(connection, validationTimeout); } else { setNetworkTimeout(connection, validationTimeout); } if (connection.isReadOnly() != isReadOnly) { connection.setReadOnly(isReadOnly); } if (connection.getAutoCommit() != isAutoCommit) { connection.setAutoCommit(isAutoCommit); } checkDriverSupport(connection); if (transactionIsolation != defaultTransactionIsolation) { //noinspection MagicConstant connection.setTransactionIsolation(transactionIsolation); } if (catalog != null) { connection.setCatalog(catalog); } if (schema != null) { connection.setSchema(schema); } executeSql(connection, config.getConnectionInitSql(), true); setNetworkTimeout(connection, networkTimeout); } catch (SQLException e) { throw new ConnectionSetupException(e); } } /** * Execute isValid() or connection test query. * * @param connection a Connection to check */ private void checkDriverSupport(final Connection connection) throws SQLException { if (!isValidChecked) { checkValidationSupport(connection); checkDefaultIsolation(connection); isValidChecked = true; } } /** * Check whether Connection.isValid() is supported, or that the user has test query configured. * * @param connection a Connection to check * @throws SQLException rethrown from the driver */ private void checkValidationSupport(final Connection connection) throws SQLException { try { if (isUseJdbc4Validation) { connection.isValid(Math.max(1, (int) MILLISECONDS.toSeconds(validationTimeout))); } else { executeSql(connection, config.getConnectionTestQuery(), false); } } catch (Exception | AbstractMethodError e) { logger.error("{} - Failed to execute{} connection test query ({}).", poolName, (isUseJdbc4Validation ? " isValid() for connection, configure" : ""), e.getMessage()); throw e; } } /** * Check the default transaction isolation of the Connection. * * @param connection a Connection to check * @throws SQLException rethrown from the driver */ private void checkDefaultIsolation(final Connection connection) throws SQLException { try { defaultTransactionIsolation = connection.getTransactionIsolation(); if (transactionIsolation == -1) { transactionIsolation = defaultTransactionIsolation; } } catch (SQLException e) { logger.warn("{} - Default transaction isolation level detection failed ({}).", poolName, e.getMessage()); if (e.getSQLState() != null && !e.getSQLState().startsWith("08")) { throw e; } } } /** * Set the query timeout, if it is supported by the driver. * * @param statement a statement to set the query timeout on * @param timeoutSec the number of seconds before timeout */ private void setQueryTimeout(final Statement statement, final int timeoutSec) { if (isQueryTimeoutSupported != FALSE) { try { statement.setQueryTimeout(timeoutSec); isQueryTimeoutSupported = TRUE; } catch (Exception e) { if (isQueryTimeoutSupported == UNINITIALIZED) { isQueryTimeoutSupported = FALSE; logger.info("{} - Failed to set query timeout for statement. ({})", poolName, e.getMessage()); } } } } /** * Set the network timeout, if isUseNetworkTimeout is true and the * driver supports it. Return the pre-existing value of the network timeout. * * @param connection the connection to set the network timeout on * @param timeoutMs the number of milliseconds before timeout * @return the pre-existing network timeout value */ private int getAndSetNetworkTimeout(final Connection connection, final long timeoutMs) { if (isNetworkTimeoutSupported != FALSE) { try { final var originalTimeout = connection.getNetworkTimeout(); connection.setNetworkTimeout(netTimeoutExecutor, (int) timeoutMs); isNetworkTimeoutSupported = TRUE; return originalTimeout; } catch (Exception | AbstractMethodError e) { if (isNetworkTimeoutSupported == UNINITIALIZED) { isNetworkTimeoutSupported = FALSE; logger.info("{} - Driver does not support get/set network timeout for connections. ({})", poolName, e.getMessage()); if (validationTimeout < SECONDS.toMillis(1)) { logger.warn("{} - A validationTimeout of less than 1 second cannot be honored on drivers without setNetworkTimeout() support.", poolName); } else if (validationTimeout % SECONDS.toMillis(1) != 0) { logger.warn("{} - A validationTimeout with fractional second granularity cannot be honored on drivers without setNetworkTimeout() support.", poolName); } } } } return 0; } /** * Set the network timeout, if isUseNetworkTimeout is true and the * driver supports it. * * @param connection the connection to set the network timeout on * @param timeoutMs the number of milliseconds before timeout * @throws SQLException throw if the connection.setNetworkTimeout() call throws */ private void setNetworkTimeout(final Connection connection, final long timeoutMs) throws SQLException { if (isNetworkTimeoutSupported == TRUE) { connection.setNetworkTimeout(netTimeoutExecutor, (int) timeoutMs); } } /** * Execute the user-specified init SQL. * * @param connection the connection to initialize * @param sql the SQL to execute * @param isCommit whether to commit the SQL after execution or not * @throws SQLException throws if the init SQL execution fails */ private void executeSql(final Connection connection, final String sql, final boolean isCommit) throws SQLException { if (sql != null) { try (var statement = connection.createStatement()) { // connection was created a few milliseconds before, so set query timeout is omitted (we assume it will succeed) statement.execute(sql); } if (isIsolateInternalQueries && !isAutoCommit) { if (isCommit) { connection.commit(); } else { connection.rollback(); } } } } private void createNetworkTimeoutExecutor(final DataSource dataSource, final String dsClassName, final String jdbcUrl) { // Temporary hack for MySQL issue: http://bugs.mysql.com/bug.php?id=75615 if ((dsClassName != null && dsClassName.contains("Mysql")) || (jdbcUrl != null && jdbcUrl.contains("mysql")) || (dataSource != null && dataSource.getClass().getName().contains("Mysql"))) { netTimeoutExecutor = new SynchronousExecutor(); } else { ThreadFactory threadFactory = config.getThreadFactory(); threadFactory = threadFactory != null ? threadFactory : new DefaultThreadFactory(poolName + ":network-timeout-executor"); ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool(threadFactory); executor.setKeepAliveTime(15, SECONDS); executor.allowCoreThreadTimeOut(true); netTimeoutExecutor = executor; } } /** * Set the loginTimeout on the specified DataSource. * * @param dataSource the DataSource */ private void setLoginTimeout(final DataSource dataSource) { if (connectionTimeout != Integer.MAX_VALUE) { try { dataSource.setLoginTimeout(Math.max(MINIMUM_LOGIN_TIMEOUT, (int) MILLISECONDS.toSeconds(500L + connectionTimeout))); } catch (Exception e) { logger.info("{} - Failed to set login timeout for data source. ({})", poolName, e.getMessage()); } } } /** * This will create a string for debug logging. Given a set of "reset bits", this * method will return a concatenated string, for example: *

... */ private static class SynchronousExecutor implements Executor { /** {@inheritDoc} */ @Override @SuppressWarnings("NullableProblems") public void execute(Runnable command) { try { command.run(); } catch (Exception t) { LoggerFactory.getLogger(PoolBase.class).debug("Failed to execute: {}", command, t); } } } 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() {} @Override default void close() {} } /** * A class that delegates to a MetricsTracker implementation. The use of a delegate * allows us to use the NopMetricsTrackerDelegate when metrics are disabled, which in * turn allows the JIT to completely optimize away to callsites to record metrics. */ static class MetricsTrackerDelegate implements IMetricsTrackerDelegate { final IMetricsTracker tracker; MetricsTrackerDelegate(IMetricsTracker tracker) { this.tracker = tracker; } @Override public void recordConnectionUsage(final PoolEntry poolEntry) { tracker.recordConnectionUsageMillis(poolEntry.getMillisSinceBorrowed()); } @Override public void recordConnectionCreated(long connectionCreatedMillis) { tracker.recordConnectionCreatedMillis(connectionCreatedMillis); } @Override public void recordBorrowTimeoutStats(long startTime) { tracker.recordConnectionAcquiredNanos(elapsedNanos(startTime)); } @Override public void recordBorrowStats(final PoolEntry poolEntry, final long startTime) { final var now = currentTime(); poolEntry.lastBorrowed = now; tracker.recordConnectionAcquiredNanos(elapsedNanos(startTime, now)); } @Override public void recordConnectionTimeout() { tracker.recordConnectionTimeout(); } @Override public void close() { tracker.close(); } } /** * A no-op implementation of the IMetricsTrackerDelegate that is used when metrics capture is * disabled. */ static final class NopMetricsTrackerDelegate implements IMetricsTrackerDelegate {} } ================================================ FILE: src/main/java/com/zaxxer/hikari/pool/PoolEntry.java ================================================ /* * Copyright (C) 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import 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.concurrent.ScheduledFuture; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import static com.zaxxer.hikari.util.ClockSource.*; import static com.zaxxer.hikari.util.ClockSource.currentTime; /** * Entry used in the ConcurrentBag to track Connection instances. * * @author Brett Wooldridge */ final class PoolEntry implements IConcurrentBagEntry { private static final Logger LOGGER = LoggerFactory.getLogger(PoolEntry.class); private static final AtomicIntegerFieldUpdater stateUpdater; Connection connection; long lastAccessed; long lastBorrowed; @SuppressWarnings("FieldCanBeLocal") private volatile int state = 0; private volatile boolean evict; private volatile ScheduledFuture endOfLife; private volatile ScheduledFuture keepalive; private final FastList openStatements; private final HikariPool hikariPool; private final boolean isReadOnly; private final boolean isAutoCommit; static { stateUpdater = AtomicIntegerFieldUpdater.newUpdater(PoolEntry.class, "state"); } PoolEntry(final Connection connection, final PoolBase pool, final boolean isReadOnly, final boolean isAutoCommit) { this.connection = connection; this.hikariPool = (HikariPool) pool; this.isReadOnly = isReadOnly; this.isAutoCommit = isAutoCommit; this.lastAccessed = currentTime(); this.openStatements = new FastList<>(Statement.class, 16); } /** * Release this entry back to the pool. */ void recycle() { if (connection != null) { this.lastAccessed = currentTime(); hikariPool.recycle(this); } } /** * Set the end of life {@link ScheduledFuture}. * * @param endOfLife this PoolEntry/Connection's end of life {@link ScheduledFuture} */ void setFutureEol(final ScheduledFuture endOfLife) { this.endOfLife = endOfLife; } public void setKeepalive(ScheduledFuture keepalive) { this.keepalive = keepalive; } Connection createProxyConnection(final ProxyLeakTask leakTask) { return ProxyFactory.getProxyConnection(this, connection, openStatements, leakTask, isReadOnly, isAutoCommit); } void resetConnectionState(final ProxyConnection proxyConnection, final int dirtyBits) throws SQLException { hikariPool.resetConnectionState(connection, proxyConnection, dirtyBits); } String getPoolName() { return hikariPool.toString(); } boolean isMarkedEvicted() { return evict; } void markEvicted() { this.evict = true; } void evict(final String closureReason) { hikariPool.closeConnection(this, closureReason); } /** Returns millis since lastBorrowed */ long getMillisSinceBorrowed() { return elapsedMillis(lastBorrowed); } PoolBase getPoolBase() { return hikariPool; } /** {@inheritDoc} */ @Override public String toString() { final var now = currentTime(); return connection + ", accessed " + elapsedDisplayString(lastAccessed, now) + " ago, " + stateToString(); } // *********************************************************************** // IConcurrentBagEntry methods // *********************************************************************** /** {@inheritDoc} */ @Override public int getState() { return stateUpdater.get(this); } /** {@inheritDoc} */ @Override public boolean compareAndSet(int expect, int update) { return stateUpdater.compareAndSet(this, expect, update); } /** {@inheritDoc} */ @Override public void setState(int update) { stateUpdater.set(this, update); } Connection close() { var eol = endOfLife; if (eol != null && !eol.isDone() && !eol.cancel(false)) { LOGGER.warn("{} - maxLifeTime expiration task cancellation unexpectedly returned false for connection {}", getPoolName(), connection); } var ka = keepalive; if (ka != null && !ka.isDone() && !ka.cancel(false)) { LOGGER.warn("{} - keepalive task cancellation unexpectedly returned false for connection {}", getPoolName(), connection); } var con = connection; connection = null; endOfLife = null; keepalive = null; return con; } private String stateToString() { switch (state) { case STATE_IN_USE: return "IN_USE"; case STATE_NOT_IN_USE: return "NOT_IN_USE"; case STATE_REMOVED: return "REMOVED"; case STATE_RESERVED: return "RESERVED"; default: return "Invalid"; } } } ================================================ FILE: src/main/java/com/zaxxer/hikari/pool/ProxyCallableStatement.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import java.sql.CallableStatement; /** * This is the proxy class for {@link CallableStatement}. * * @author Brett Wooldridge */ public abstract class ProxyCallableStatement extends ProxyPreparedStatement implements CallableStatement { protected ProxyCallableStatement(ProxyConnection connection, CallableStatement statement) { super(connection, statement); } // ********************************************************************** // Overridden java.sql.CallableStatement Methods // ********************************************************************** } ================================================ FILE: src/main/java/com/zaxxer/hikari/pool/ProxyConnection.java ================================================ /* * 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 com.zaxxer.hikari.util.FastList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.sql.*; import java.util.HashSet; import java.util.Set; import java.util.concurrent.Executor; import static com.zaxxer.hikari.SQLExceptionOverride.Override.*; /** * This is the proxy class for {@link Connection}. * * @author Brett Wooldridge */ public abstract class ProxyConnection implements Connection { static final int DIRTY_BIT_READONLY = 0b000001; static final int DIRTY_BIT_AUTOCOMMIT = 0b000010; static final int DIRTY_BIT_ISOLATION = 0b000100; static final int DIRTY_BIT_CATALOG = 0b001000; static final int DIRTY_BIT_NETTIMEOUT = 0b010000; static final int DIRTY_BIT_SCHEMA = 0b100000; private static final Logger LOGGER; private static final Set ERROR_STATES; private static final Set ERROR_CODES; @SuppressWarnings("WeakerAccess") protected Connection delegate; private final PoolEntry poolEntry; private final ProxyLeakTask leakTask; private final FastList openStatements; private int dirtyBits; private boolean isCommitStateDirty; private boolean isReadOnly; private boolean isAutoCommit; private int networkTimeout; private int transactionIsolation; private String dbcatalog; private String dbschema; // static initializer static { LOGGER = LoggerFactory.getLogger(ProxyConnection.class); ERROR_STATES = new HashSet<>(); ERROR_STATES.add("0A000"); // FEATURE UNSUPPORTED ERROR_STATES.add("57P01"); // ADMIN SHUTDOWN ERROR_STATES.add("57P02"); // CRASH SHUTDOWN ERROR_STATES.add("57P03"); // CANNOT CONNECT NOW ERROR_STATES.add("01002"); // SQL92 disconnect error ERROR_STATES.add("JZ0C0"); // Sybase disconnect error ERROR_STATES.add("JZ0C1"); // Sybase disconnect error ERROR_CODES = new HashSet<>(); ERROR_CODES.add(500150); ERROR_CODES.add(2399); ERROR_CODES.add(1105); } protected ProxyConnection(final PoolEntry poolEntry, final Connection connection, final FastList openStatements, final ProxyLeakTask leakTask, final boolean isReadOnly, final boolean isAutoCommit) { this.poolEntry = poolEntry; this.delegate = connection; this.openStatements = openStatements; this.leakTask = leakTask; this.isReadOnly = isReadOnly; this.isAutoCommit = isAutoCommit; } /** {@inheritDoc} */ @Override public final String toString() { return this.getClass().getSimpleName() + '@' + System.identityHashCode(this) + " wrapping " + delegate; } // *********************************************************************** // Connection State Accessors // *********************************************************************** final boolean getAutoCommitState() { return isAutoCommit; } final String getCatalogState() { return dbcatalog; } final String getSchemaState() { return dbschema; } final int getTransactionIsolationState() { return transactionIsolation; } final boolean getReadOnlyState() { return isReadOnly; } final int getNetworkTimeoutState() { return networkTimeout; } // *********************************************************************** // Internal methods // *********************************************************************** final PoolEntry getPoolEntry() { return poolEntry; } @SuppressWarnings("ConstantConditions") final SQLException checkException(SQLException sqle) { var evict = false; SQLException nse = sqle; final var exceptionOverride = poolEntry.getPoolBase().exceptionOverride; for (int depth = 0; delegate != ClosedConnection.CLOSED_CONNECTION && nse != null && depth < 10; depth++) { final var sqlState = nse.getSQLState(); final var shouldEvict = exceptionOverride != null ? exceptionOverride.adjudicate(nse) : CONTINUE_EVICT; if (shouldEvict == DO_NOT_EVICT) { break; } else if (sqlState != null && sqlState.startsWith("08") || ERROR_STATES.contains(sqlState) || ERROR_CODES.contains(nse.getErrorCode()) || shouldEvict == MUST_EVICT) { // broken connection evict = true; break; } else { nse = nse.getNextException(); } } if (evict) { var exception = (nse != null) ? nse : sqle; LOGGER.warn("{} - Connection {} marked as broken because of SQLSTATE({}), ErrorCode({})", poolEntry.getPoolName(), delegate, exception.getSQLState(), exception.getErrorCode(), exception); leakTask.cancel(); poolEntry.evict("(connection is broken)"); delegate = ClosedConnection.CLOSED_CONNECTION; } return sqle; } final synchronized void untrackStatement(final Statement statement) { openStatements.remove(statement); } final void markCommitStateDirty() { if (!isAutoCommit) { isCommitStateDirty = true; } } void cancelLeakTask() { leakTask.cancel(); } private synchronized T trackStatement(final T statement) { openStatements.add(statement); return statement; } @SuppressWarnings("EmptyTryBlock") private synchronized void closeStatements() { final var size = openStatements.size(); if (size > 0) { for (int i = 0; i < size && delegate != ClosedConnection.CLOSED_CONNECTION; i++) { try (Statement ignored = openStatements.get(i)) { // automatic resource cleanup } catch (SQLException 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; } } openStatements.clear(); } } // ********************************************************************** // "Overridden" java.sql.Connection Methods // ********************************************************************** /** {@inheritDoc} */ @Override public final void close() throws SQLException { // Closing statements can cause connection eviction, so this must run before the conditional below closeStatements(); if (delegate != ClosedConnection.CLOSED_CONNECTION) { leakTask.cancel(); try { if (isCommitStateDirty && !isAutoCommit) { delegate.rollback(); LOGGER.debug("{} - Executed rollback on connection {} due to dirty commit state on close().", poolEntry.getPoolName(), delegate); } if (dirtyBits != 0) { poolEntry.resetConnectionState(this, dirtyBits); } delegate.clearWarnings(); } catch (SQLException e) { // when connections are aborted, exceptions are often thrown that should not reach the application if (!poolEntry.isMarkedEvicted()) { throw checkException(e); } } finally { delegate = ClosedConnection.CLOSED_CONNECTION; poolEntry.recycle(); } } } /** {@inheritDoc} */ @Override @SuppressWarnings("RedundantThrows") public boolean isClosed() throws SQLException { return (delegate == ClosedConnection.CLOSED_CONNECTION); } /** {@inheritDoc} */ @Override public Statement createStatement() throws SQLException { return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement())); } /** {@inheritDoc} */ @Override public Statement createStatement(int resultSetType, int concurrency) throws SQLException { return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement(resultSetType, concurrency))); } /** {@inheritDoc} */ @Override public Statement createStatement(int resultSetType, int concurrency, int holdability) throws SQLException { return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement(resultSetType, concurrency, holdability))); } /** {@inheritDoc} */ @Override public CallableStatement prepareCall(String sql) throws SQLException { return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql))); } /** {@inheritDoc} */ @Override public CallableStatement prepareCall(String sql, int resultSetType, int concurrency) throws SQLException { return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql, resultSetType, concurrency))); } /** {@inheritDoc} */ @Override public CallableStatement prepareCall(String sql, int resultSetType, int concurrency, int holdability) throws SQLException { return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql, resultSetType, concurrency, holdability))); } /** {@inheritDoc} */ @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql))); } /** {@inheritDoc} */ @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, autoGeneratedKeys))); } /** {@inheritDoc} */ @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int concurrency) throws SQLException { return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, resultSetType, concurrency))); } /** {@inheritDoc} */ @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int concurrency, int holdability) throws SQLException { return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, resultSetType, concurrency, holdability))); } /** {@inheritDoc} */ @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, columnIndexes))); } /** {@inheritDoc} */ @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, columnNames))); } /** {@inheritDoc} */ @Override public DatabaseMetaData getMetaData() throws SQLException { markCommitStateDirty(); return ProxyFactory.getProxyDatabaseMetaData(this, delegate.getMetaData()); } /** {@inheritDoc} */ @Override public void commit() throws SQLException { delegate.commit(); isCommitStateDirty = false; } /** {@inheritDoc} */ @Override public void rollback() throws SQLException { delegate.rollback(); isCommitStateDirty = false; } /** {@inheritDoc} */ @Override public void rollback(Savepoint savepoint) throws SQLException { delegate.rollback(savepoint); isCommitStateDirty = true; } /** {@inheritDoc} */ @Override public boolean getAutoCommit() throws SQLException { if ((dirtyBits & DIRTY_BIT_AUTOCOMMIT) != 0) { return isAutoCommit; } return delegate.getAutoCommit(); } /** {@inheritDoc} */ @Override public void setAutoCommit(boolean autoCommit) throws SQLException { delegate.setAutoCommit(autoCommit); isAutoCommit = autoCommit; dirtyBits |= DIRTY_BIT_AUTOCOMMIT; } /** {@inheritDoc} */ @Override public boolean isReadOnly() throws SQLException { if ((dirtyBits & DIRTY_BIT_READONLY) != 0) { return isReadOnly; } return delegate.isReadOnly(); } /** {@inheritDoc} */ @Override public void setReadOnly(boolean readOnly) throws SQLException { delegate.setReadOnly(readOnly); isReadOnly = readOnly; dirtyBits |= DIRTY_BIT_READONLY; } /** {@inheritDoc} */ @Override public int getTransactionIsolation() throws SQLException { if ((dirtyBits & DIRTY_BIT_ISOLATION) != 0) { return transactionIsolation; } return delegate.getTransactionIsolation(); } /** {@inheritDoc} */ @Override public void setTransactionIsolation(int level) throws SQLException { delegate.setTransactionIsolation(level); transactionIsolation = level; dirtyBits |= DIRTY_BIT_ISOLATION; } /** {@inheritDoc} */ @Override public String getCatalog() throws SQLException { if ((dirtyBits & DIRTY_BIT_CATALOG) != 0) { return dbcatalog; } return delegate.getCatalog(); } /** {@inheritDoc} */ @Override public void setCatalog(String catalog) throws SQLException { delegate.setCatalog(catalog); dbcatalog = catalog; dirtyBits |= DIRTY_BIT_CATALOG; } /** {@inheritDoc} */ @Override public int getNetworkTimeout() throws SQLException { if ((dirtyBits & DIRTY_BIT_NETTIMEOUT) != 0) { return networkTimeout; } return delegate.getNetworkTimeout(); } /** {@inheritDoc} */ @Override public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { delegate.setNetworkTimeout(executor, milliseconds); networkTimeout = milliseconds; dirtyBits |= DIRTY_BIT_NETTIMEOUT; } /** {@inheritDoc} */ @Override public String getSchema() throws SQLException { if ((dirtyBits & DIRTY_BIT_SCHEMA) != 0) { return dbschema; } return delegate.getSchema(); } /** {@inheritDoc} */ @Override public void setSchema(String schema) throws SQLException { delegate.setSchema(schema); dbschema = delegate.getSchema(); dirtyBits |= DIRTY_BIT_SCHEMA; } /** {@inheritDoc} */ @Override public final boolean isWrapperFor(Class iface) throws SQLException { return iface.isInstance(delegate) || (delegate != null && delegate.isWrapperFor(iface)); } /** {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public final T unwrap(Class iface) throws SQLException { if (iface.isInstance(delegate)) { return (T) delegate; } else if (delegate != null) { return delegate.unwrap(iface); } throw new SQLException("Wrapped connection is not an instance of " + iface); } // ********************************************************************** // Private classes // ********************************************************************** private static final class ClosedConnection { static final Connection CLOSED_CONNECTION = getClosedConnection(); private static Connection getClosedConnection() { InvocationHandler handler = (proxy, method, args) -> { final String methodName = method.getName(); switch (methodName) { case "isClosed": return Boolean.TRUE; case "isValid": return Boolean.FALSE; case "abort": return Void.TYPE; case "close": return Void.TYPE; case "toString": return ClosedConnection.class.getCanonicalName(); } throw new SQLException("Connection is closed"); }; return (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), new Class[] { Connection.class }, handler); } } } ================================================ FILE: src/main/java/com/zaxxer/hikari/pool/ProxyDatabaseMetaData.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; /** * This is the proxy class for {@link DatabaseMetaData}. * * @author Brett Wooldridge * @author Yanming Zhou */ public abstract class ProxyDatabaseMetaData implements DatabaseMetaData { protected final ProxyConnection connection; @SuppressWarnings("WeakerAccess") protected final DatabaseMetaData delegate; ProxyDatabaseMetaData(ProxyConnection connection, DatabaseMetaData metaData) { this.connection = connection; this.delegate = metaData; } final SQLException checkException(SQLException e) { return connection.checkException(e); } /** {@inheritDoc} */ @Override public final String toString() { final var delegateToString = delegate.toString(); return this.getClass().getSimpleName() + '@' + System.identityHashCode(this) + " wrapping " + delegateToString; } // ********************************************************************** // Overridden java.sql.DatabaseMetaData Methods // ********************************************************************** /** {@inheritDoc} */ @Override public final Connection getConnection() { return connection; } @Override public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) throws SQLException { var resultSet = delegate.getProcedures(catalog, schemaPattern, procedureNamePattern); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) throws SQLException { var resultSet = delegate.getProcedureColumns(catalog, schemaPattern, procedureNamePattern, columnNamePattern); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) throws SQLException { var resultSet = delegate.getTables(catalog, schemaPattern, tableNamePattern, types); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getSchemas() throws SQLException { var resultSet = delegate.getSchemas(); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getCatalogs() throws SQLException { var resultSet = delegate.getCatalogs(); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getTableTypes() throws SQLException { var resultSet = delegate.getTableTypes(); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException { var resultSet = delegate.getColumns(catalog, schemaPattern, tableNamePattern, columnNamePattern); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) throws SQLException { var resultSet = delegate.getColumnPrivileges(catalog, schema, table, columnNamePattern); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) throws SQLException { var resultSet = delegate.getTablePrivileges(catalog, schemaPattern, tableNamePattern); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) throws SQLException { var resultSet = delegate.getBestRowIdentifier(catalog, schema, table, scope, nullable); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException { var resultSet = delegate.getVersionColumns(catalog, schema, table); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException { var resultSet = delegate.getPrimaryKeys(catalog, schema, table); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException { var resultSet = delegate.getImportedKeys(catalog, schema, table); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException { var resultSet = delegate.getExportedKeys(catalog, schema, table); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTable, String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException { var resultSet = delegate.getCrossReference(parentCatalog, parentSchema, parentTable, foreignCatalog, foreignSchema, foreignTable); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getTypeInfo() throws SQLException { var resultSet = delegate.getTypeInfo(); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) throws SQLException { var resultSet = delegate.getIndexInfo(catalog, schema, table, unique, approximate); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) throws SQLException { var resultSet = delegate.getUDTs(catalog, schemaPattern, typeNamePattern, types); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException { var resultSet = delegate.getSuperTypes(catalog, schemaPattern, typeNamePattern); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) throws SQLException { var resultSet = delegate.getSuperTables(catalog, schemaPattern, tableNamePattern); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) throws SQLException { var resultSet = delegate.getAttributes(catalog, schemaPattern, typeNamePattern, attributeNamePattern); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException { var resultSet = delegate.getSchemas(catalog, schemaPattern); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getClientInfoProperties() throws SQLException { var resultSet = delegate.getClientInfoProperties(); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) throws SQLException { var resultSet = delegate.getFunctions(catalog, schemaPattern, functionNamePattern); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) throws SQLException { var resultSet = delegate.getFunctionColumns(catalog, schemaPattern, functionNamePattern, columnNamePattern); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } @Override public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException { var resultSet = delegate.getPseudoColumns(catalog, schemaPattern, tableNamePattern, columnNamePattern); var statement = resultSet.getStatement(); if (statement != null) { statement = ProxyFactory.getProxyStatement(connection, statement); } return ProxyFactory.getProxyResultSet(connection, (ProxyStatement) statement, resultSet); } /** {@inheritDoc} */ @Override public final boolean isWrapperFor(Class iface) throws SQLException { return iface.isInstance(delegate) || (delegate != null && delegate.isWrapperFor(iface)); } /** {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public final T unwrap(Class iface) throws SQLException { if (iface.isInstance(delegate)) { return (T) delegate; } else if (delegate != null) { return delegate.unwrap(iface); } throw new SQLException("Wrapped DatabaseMetaData is not an instance of " + iface); } } ================================================ FILE: src/main/java/com/zaxxer/hikari/pool/ProxyFactory.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import java.sql.*; import com.zaxxer.hikari.util.FastList; /** * A factory class that produces proxies around instances of the standard * JDBC interfaces. * * @author Brett Wooldridge */ public final class ProxyFactory { private ProxyFactory() { // unconstructable } /** * Create a proxy for the specified {@link Connection} instance. * @param poolEntry 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 isReadOnly the default readOnly state of the connection * @param isAutoCommit the default autoCommit state of the connection * @return a proxy that wraps the specified {@link Connection} */ static ProxyConnection getProxyConnection(final PoolEntry poolEntry, final Connection connection, final FastList openStatements, final ProxyLeakTask leakTask, final boolean isReadOnly, final boolean isAutoCommit) { // Body is replaced (injected) by JavassistProxyFactory throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run."); } static Statement getProxyStatement(final ProxyConnection connection, final Statement statement) { // Body is replaced (injected) by JavassistProxyFactory throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run."); } static CallableStatement getProxyCallableStatement(final ProxyConnection connection, final CallableStatement statement) { // Body is replaced (injected) by JavassistProxyFactory throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run."); } static PreparedStatement getProxyPreparedStatement(final ProxyConnection connection, final PreparedStatement statement) { // Body is replaced (injected) by JavassistProxyFactory throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run."); } static ResultSet getProxyResultSet(final ProxyConnection connection, final ProxyStatement statement, final ResultSet resultSet) { // Body is replaced (injected) by JavassistProxyFactory throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run."); } static DatabaseMetaData getProxyDatabaseMetaData(final ProxyConnection connection, final DatabaseMetaData metaData) { // Body is replaced (injected) by JavassistProxyFactory throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run."); } } ================================================ FILE: src/main/java/com/zaxxer/hikari/pool/ProxyLeakTask.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A Runnable that is scheduled in the future to report leaks. The ScheduledFuture is * cancelled if the connection is closed before the leak time expires. * * @author Brett Wooldridge */ class ProxyLeakTask implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(ProxyLeakTask.class); static final ProxyLeakTask NO_LEAK; private ScheduledFuture scheduledFuture; private String connectionName; private Exception exception; private String threadName; private boolean isLeaked; static { NO_LEAK = new ProxyLeakTask() { @Override void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold) {} @Override public void run() {} @Override public void cancel() {} }; } ProxyLeakTask(final PoolEntry poolEntry) { this.exception = new Exception("Apparent connection leak detected"); this.threadName = Thread.currentThread().getName(); this.connectionName = poolEntry.connection.toString(); } private ProxyLeakTask() { } void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold) { scheduledFuture = executorService.schedule(this, leakDetectionThreshold, TimeUnit.MILLISECONDS); } /** {@inheritDoc} */ @Override public void run() { isLeaked = true; final var stackTrace = exception.getStackTrace(); final var trace = new StackTraceElement[stackTrace.length - 5]; System.arraycopy(stackTrace, 5, trace, 0, trace.length); exception.setStackTrace(trace); LOGGER.warn("Connection leak detection triggered for {} on thread {}, stack trace follows", connectionName, threadName, exception); } void cancel() { scheduledFuture.cancel(false); if (isLeaked) { LOGGER.info("Previously reported leaked connection {} on thread {} was returned to the pool (unleaked)", connectionName, threadName); } } } ================================================ FILE: src/main/java/com/zaxxer/hikari/pool/ProxyLeakTaskFactory.java ================================================ /* * 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) { var task = new ProxyLeakTask(poolEntry); task.schedule(executorService, leakDetectionThreshold); return task; } } ================================================ FILE: src/main/java/com/zaxxer/hikari/pool/ProxyPreparedStatement.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * This is the proxy class for {@link PreparedStatement}. * * @author Brett Wooldridge */ public abstract class ProxyPreparedStatement extends ProxyStatement implements PreparedStatement { ProxyPreparedStatement(ProxyConnection connection, PreparedStatement statement) { super(connection, statement); } // ********************************************************************** // Overridden java.sql.PreparedStatement Methods // ********************************************************************** /** {@inheritDoc} */ @Override public boolean execute() throws SQLException { connection.markCommitStateDirty(); return ((PreparedStatement) delegate).execute(); } /** {@inheritDoc} */ @Override public ResultSet executeQuery() throws SQLException { connection.markCommitStateDirty(); var resultSet = ((PreparedStatement) delegate).executeQuery(); return ProxyFactory.getProxyResultSet(connection, this, resultSet); } /** {@inheritDoc} */ @Override public int executeUpdate() throws SQLException { connection.markCommitStateDirty(); return ((PreparedStatement) delegate).executeUpdate(); } /** {@inheritDoc} */ @Override public long executeLargeUpdate() throws SQLException { connection.markCommitStateDirty(); return ((PreparedStatement) delegate).executeLargeUpdate(); } } ================================================ FILE: src/main/java/com/zaxxer/hikari/pool/ProxyResultSet.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; /** * This is the proxy class for {@link ResultSet}. * * @author Brett Wooldridge * @author Yanming Zhou */ public abstract class ProxyResultSet implements ResultSet { protected final ProxyConnection connection; protected final ProxyStatement statement; final ResultSet delegate; protected ProxyResultSet(ProxyConnection connection, ProxyStatement statement, ResultSet resultSet) { this.connection = connection; this.statement = statement; this.delegate = resultSet; } final SQLException checkException(SQLException e) { return connection.checkException(e); } /** {@inheritDoc} */ @Override public String toString() { return this.getClass().getSimpleName() + '@' + System.identityHashCode(this) + " wrapping " + delegate; } // ********************************************************************** // Overridden java.sql.ResultSet Methods // ********************************************************************** /** {@inheritDoc} */ @Override public final Statement getStatement() throws SQLException { return statement; } /** {@inheritDoc} */ @Override public void updateRow() throws SQLException { connection.markCommitStateDirty(); delegate.updateRow(); } /** {@inheritDoc} */ @Override public void insertRow() throws SQLException { connection.markCommitStateDirty(); delegate.insertRow(); } /** {@inheritDoc} */ @Override public void deleteRow() throws SQLException { connection.markCommitStateDirty(); delegate.deleteRow(); } /** {@inheritDoc} */ @Override public final boolean isWrapperFor(Class iface) throws SQLException { return iface.isInstance(delegate) || (delegate != null && delegate.isWrapperFor(iface)); } /** {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public final T unwrap(Class iface) throws SQLException { if (iface.isInstance(delegate)) { return (T) delegate; } else if (delegate != null) { return delegate.unwrap(iface); } throw new SQLException("Wrapped ResultSet is not an instance of " + iface); } } ================================================ FILE: src/main/java/com/zaxxer/hikari/pool/ProxyStatement.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; /** * This is the proxy class for {@link Statement}. * * @author Brett Wooldridge * @author Yanming Zhou */ public abstract class ProxyStatement implements Statement { protected final ProxyConnection connection; final Statement delegate; private boolean isClosed; private ResultSet proxyResultSet; ProxyStatement(ProxyConnection connection, Statement statement) { this.connection = connection; this.delegate = statement; } final SQLException checkException(SQLException e) { return connection.checkException(e); } /** {@inheritDoc} */ @Override public final String toString() { final var delegateToString = delegate.toString(); return this.getClass().getSimpleName() + '@' + System.identityHashCode(this) + " wrapping " + delegateToString; } // ********************************************************************** // Overridden java.sql.Statement Methods // ********************************************************************** /** {@inheritDoc} */ @Override public final void close() throws SQLException { synchronized (this) { if (isClosed) { return; } isClosed = true; } connection.untrackStatement(delegate); try { delegate.close(); } catch (SQLException e) { throw connection.checkException(e); } } /** {@inheritDoc} */ @Override public Connection getConnection() throws SQLException { return connection; } /** {@inheritDoc} */ @Override public boolean execute(String sql) throws SQLException { connection.markCommitStateDirty(); return delegate.execute(sql); } /** {@inheritDoc} */ @Override public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { connection.markCommitStateDirty(); return delegate.execute(sql, autoGeneratedKeys); } /** {@inheritDoc} */ @Override public ResultSet executeQuery(String sql) throws SQLException { connection.markCommitStateDirty(); ResultSet resultSet = delegate.executeQuery(sql); return ProxyFactory.getProxyResultSet(connection, this, resultSet); } /** {@inheritDoc} */ @Override public int executeUpdate(String sql) throws SQLException { connection.markCommitStateDirty(); return delegate.executeUpdate(sql); } /** {@inheritDoc} */ @Override public int[] executeBatch() throws SQLException { connection.markCommitStateDirty(); return delegate.executeBatch(); } /** {@inheritDoc} */ @Override public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { connection.markCommitStateDirty(); return delegate.executeUpdate(sql, autoGeneratedKeys); } /** {@inheritDoc} */ @Override public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { connection.markCommitStateDirty(); return delegate.executeUpdate(sql, columnIndexes); } /** {@inheritDoc} */ @Override public int executeUpdate(String sql, String[] columnNames) throws SQLException { connection.markCommitStateDirty(); return delegate.executeUpdate(sql, columnNames); } /** {@inheritDoc} */ @Override public boolean execute(String sql, int[] columnIndexes) throws SQLException { connection.markCommitStateDirty(); return delegate.execute(sql, columnIndexes); } /** {@inheritDoc} */ @Override public boolean execute(String sql, String[] columnNames) throws SQLException { connection.markCommitStateDirty(); return delegate.execute(sql, columnNames); } /** {@inheritDoc} */ @Override public long[] executeLargeBatch() throws SQLException { connection.markCommitStateDirty(); return delegate.executeLargeBatch(); } /** {@inheritDoc} */ @Override public long executeLargeUpdate(String sql) throws SQLException { connection.markCommitStateDirty(); return delegate.executeLargeUpdate(sql); } /** {@inheritDoc} */ @Override public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException { connection.markCommitStateDirty(); return delegate.executeLargeUpdate(sql, autoGeneratedKeys); } /** {@inheritDoc} */ @Override public long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException { connection.markCommitStateDirty(); return delegate.executeLargeUpdate(sql, columnIndexes); } /** {@inheritDoc} */ @Override public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException { connection.markCommitStateDirty(); return delegate.executeLargeUpdate(sql, columnNames); } /** {@inheritDoc} */ @Override public ResultSet getResultSet() throws SQLException { final var resultSet = delegate.getResultSet(); if (resultSet != null) { if (proxyResultSet == null || ((ProxyResultSet) proxyResultSet).delegate != resultSet) { proxyResultSet = ProxyFactory.getProxyResultSet(connection, this, resultSet); } } else { proxyResultSet = null; } return proxyResultSet; } /** {@inheritDoc} */ @Override public ResultSet getGeneratedKeys() throws SQLException { var resultSet = delegate.getGeneratedKeys(); if (proxyResultSet == null || ((ProxyResultSet) proxyResultSet).delegate != resultSet) { proxyResultSet = ProxyFactory.getProxyResultSet(connection, this, resultSet); } return proxyResultSet; } /** {@inheritDoc} */ @Override public final boolean isWrapperFor(Class iface) throws SQLException { return iface.isInstance(delegate) || (delegate != null && delegate.isWrapperFor(iface)); } /** {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public final T unwrap(Class iface) throws SQLException { if (iface.isInstance(delegate)) { return (T) delegate; } else if (delegate != null) { return delegate.unwrap(iface); } throw new SQLException("Wrapped statement is not an instance of " + iface); } } ================================================ FILE: src/main/java/com/zaxxer/hikari/util/ClockSource.java ================================================ /* * Copyright (C) 2015 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.util; import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.MICROSECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import java.util.concurrent.TimeUnit; /** * A resolution-independent provider of current time-stamps and elapsed time * calculations. * * @author Brett Wooldridge * @hidden */ public interface ClockSource { static ClockSource CLOCK = Factory.create(); /** * Get the current time-stamp (resolution is opaque). * * @return the current time-stamp */ static long currentTime() { return CLOCK.currentTime0(); } long currentTime0(); /** * Convert an opaque time-stamp returned by currentTime() into * milliseconds. * * @param time an opaque time-stamp returned by an instance of this class * @return the time-stamp in milliseconds */ static long toMillis(long time) { return CLOCK.toMillis0(time); } long toMillis0(long time); /** * Convert an opaque time-stamp returned by currentTime() into * nanoseconds. * * @param time an opaque time-stamp returned by an instance of this class * @return the time-stamp in nanoseconds */ static long toNanos(long time) { return CLOCK.toNanos0(time); } long toNanos0(long time); /** * Convert an opaque time-stamp returned by currentTime() into an * elapsed time in milliseconds, based on the current instant in time. * * @param startTime an opaque time-stamp returned by an instance of this class * @return the elapsed time between startTime and now in milliseconds */ static long elapsedMillis(long startTime) { return CLOCK.elapsedMillis0(startTime); } long elapsedMillis0(long startTime); /** * Get the difference in milliseconds between two opaque time-stamps returned * by currentTime(). * * @param startTime an opaque time-stamp returned by an instance of this class * @param endTime an opaque time-stamp returned by an instance of this class * @return the elapsed time between startTime and endTime in milliseconds */ static long elapsedMillis(long startTime, long endTime) { return CLOCK.elapsedMillis0(startTime, endTime); } long elapsedMillis0(long startTime, long endTime); /** * Convert an opaque time-stamp returned by currentTime() into an * elapsed time in milliseconds, based on the current instant in time. * * @param startTime an opaque time-stamp returned by an instance of this class * @return the elapsed time between startTime and now in milliseconds */ static long elapsedNanos(long startTime) { return CLOCK.elapsedNanos0(startTime); } long elapsedNanos0(long startTime); /** * Get the difference in nanoseconds between two opaque time-stamps returned * by currentTime(). * * @param startTime an opaque time-stamp returned by an instance of this class * @param endTime an opaque time-stamp returned by an instance of this class * @return the elapsed time between startTime and endTime in nanoseconds */ static long elapsedNanos(long startTime, long endTime) { return CLOCK.elapsedNanos0(startTime, endTime); } long elapsedNanos0(long startTime, long endTime); /** * Return the specified opaque time-stamp plus the specified number of milliseconds. * * @param time an opaque time-stamp * @param millis milliseconds to add * @return a new opaque time-stamp */ static long plusMillis(long time, long millis) { return CLOCK.plusMillis0(time, millis); } long plusMillis0(long time, long millis); /** * Get the TimeUnit the ClockSource is denominated in. * @return the TimeUnit used by the ClockSource */ static TimeUnit getSourceTimeUnit() { return CLOCK.getSourceTimeUnit0(); } TimeUnit getSourceTimeUnit0(); /** * Get a String representation of the elapsed time in appropriate magnitude terminology. * * @param startTime an opaque time-stamp * @param endTime an opaque time-stamp * @return a string representation of the elapsed time interval */ static String elapsedDisplayString(long startTime, long endTime) { return CLOCK.elapsedDisplayString0(startTime, endTime); } default String elapsedDisplayString0(long startTime, long endTime) { long elapsedNanos = elapsedNanos0(startTime, endTime); StringBuilder sb = new StringBuilder(elapsedNanos < 0 ? "-" : ""); elapsedNanos = Math.abs(elapsedNanos); for (TimeUnit unit : TIMEUNITS_DESCENDING) { long converted = unit.convert(elapsedNanos, NANOSECONDS); if (converted > 0) { sb.append(converted).append(TIMEUNIT_DISPLAY_VALUES[unit.ordinal()]); elapsedNanos -= NANOSECONDS.convert(converted, unit); } } return sb.toString(); } TimeUnit[] TIMEUNITS_DESCENDING = {DAYS, HOURS, MINUTES, SECONDS, MILLISECONDS, MICROSECONDS, NANOSECONDS}; String[] TIMEUNIT_DISPLAY_VALUES = {"ns", "µs", "ms", "s", "m", "h", "d"}; /** * Factory class used to create a platform-specific ClockSource. * @hidden */ class Factory { private static ClockSource create() { String os = System.getProperty("os.name"); if ("Mac OS X".equals(os)) { return new MillisecondClockSource(); } return new NanosecondClockSource(); } } /** * A ClockSource that uses System.currentTimeMillis() for time-stamps. * This is the default implementation on Mac OS X, and is used when * System.nanoTime() is not available. * @hidden */ final class MillisecondClockSource implements ClockSource { /** {@inheritDoc} */ @Override public long currentTime0() { return System.currentTimeMillis(); } /** {@inheritDoc} */ @Override public long elapsedMillis0(final long startTime) { return System.currentTimeMillis() - startTime; } /** {@inheritDoc} */ @Override public long elapsedMillis0(final long startTime, final long endTime) { return endTime - startTime; } /** {@inheritDoc} */ @Override public long elapsedNanos0(final long startTime) { return MILLISECONDS.toNanos(System.currentTimeMillis() - startTime); } /** {@inheritDoc} */ @Override public long elapsedNanos0(final long startTime, final long endTime) { return MILLISECONDS.toNanos(endTime - startTime); } /** {@inheritDoc} */ @Override public long toMillis0(final long time) { return time; } /** {@inheritDoc} */ @Override public long toNanos0(final long time) { return MILLISECONDS.toNanos(time); } /** {@inheritDoc} */ @Override public long plusMillis0(final long time, final long millis) { return time + millis; } /** {@inheritDoc} */ @Override public TimeUnit getSourceTimeUnit0() { return MILLISECONDS; } } /** * A ClockSource that uses System.nanoTime() for time-stamps. * This is the default implementation on all platforms except Mac OS X. * @hidden */ class NanosecondClockSource implements ClockSource { /** {@inheritDoc} */ @Override public long currentTime0() { return System.nanoTime(); } /** {@inheritDoc} */ @Override public long toMillis0(final long time) { return NANOSECONDS.toMillis(time); } /** {@inheritDoc} */ @Override public long toNanos0(final long time) { return time; } /** {@inheritDoc} */ @Override public long elapsedMillis0(final long startTime) { return NANOSECONDS.toMillis(System.nanoTime() - startTime); } /** {@inheritDoc} */ @Override public long elapsedMillis0(final long startTime, final long endTime) { return NANOSECONDS.toMillis(endTime - startTime); } /** {@inheritDoc} */ @Override public long elapsedNanos0(final long startTime) { return System.nanoTime() - startTime; } /** {@inheritDoc} */ @Override public long elapsedNanos0(final long startTime, final long endTime) { return endTime - startTime; } /** {@inheritDoc} */ @Override public long plusMillis0(final long time, final long millis) { return time + MILLISECONDS.toNanos(millis); } /** {@inheritDoc} */ @Override public TimeUnit getSourceTimeUnit0() { return NANOSECONDS; } } } ================================================ FILE: src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.util; import com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.SynchronousQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import static com.zaxxer.hikari.util.ClockSource.currentTime; import static com.zaxxer.hikari.util.ClockSource.elapsedNanos; import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.*; import static java.util.concurrent.TimeUnit.MICROSECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.locks.LockSupport.parkNanos; /** * This is a specialized concurrent bag that achieves superior performance * to LinkedBlockingQueue and LinkedTransferQueue for the purposes of a * connection pool. It uses ThreadLocal storage when possible to avoid * locks, but resorts to scanning a common collection if there are no * available items in the ThreadLocal list. Not-in-use items in the * ThreadLocal lists can be "stolen" when the borrowing thread has none * of its own. It is a "lock-less" implementation using a specialized * AbstractQueuedLongSynchronizer to manage cross-thread signaling. *

* Note that items that are "borrowed" from the bag are not actually * removed from any collection, so garbage collection will not occur * even if the reference is abandoned. Thus care must be taken to * "requite" borrowed objects otherwise a memory leak will result. Only * the "remove" method can completely remove an object from the bag. * * @author Brett Wooldridge * * @param the templated type to store in the bag * @hidden */ @SuppressWarnings("DuplicatedCode") public class ConcurrentBag implements AutoCloseable { private static final Logger LOGGER = LoggerFactory.getLogger(ConcurrentBag.class); private final CopyOnWriteArrayList sharedList; private final boolean useWeakThreadLocals; private final ThreadLocal> threadLocalList; private final IBagStateListener listener; private final AtomicInteger waiters; private volatile boolean closed; private final SynchronousQueue handoffQueue; /** * This interface defines the contract for an entry in the ConcurrentBag. * It provides methods to manage the state of the entry, which can be * "not in use", "in use", "removed", or "reserved". * @hidden */ public interface IConcurrentBagEntry { int STATE_NOT_IN_USE = 0; int STATE_IN_USE = 1; int STATE_REMOVED = -1; int STATE_RESERVED = -2; boolean compareAndSet(int expectState, int newState); void setState(int newState); int getState(); } /** * This interface defines a listener that will be notified when items * are added to the bag, allowing for external management of bag state. * The listener is typically used to inform the pool manager about the * number of items waiting for a bag item to become available. * @hidden */ public interface IBagStateListener { void addBagItem(int waiting); } /** * Construct a ConcurrentBag with the specified listener. * * @param listener the IBagStateListener to attach to this bag */ public ConcurrentBag(final IBagStateListener listener) { this.listener = listener; this.useWeakThreadLocals = useWeakThreadLocals(); this.handoffQueue = new SynchronousQueue<>(true); this.waiters = new AtomicInteger(); this.sharedList = new CopyOnWriteArrayList<>(); this.threadLocalList = ThreadLocal.withInitial(() -> useWeakThreadLocals ? new ArrayList<>(16) : new FastList<>(IConcurrentBagEntry.class, 16) ); } /** * The method will borrow a BagEntry from the bag, blocking for the * specified timeout if none are available. * * @param timeout how long to wait before giving up, in units of unit * @param timeUnit a TimeUnit determining how to interpret the timeout parameter * @return a borrowed instance from the bag or null if a timeout occurs * @throws InterruptedException if interrupted while waiting */ public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException { // Try the thread-local list first final var list = threadLocalList.get(); for (var i = list.size() - 1; i >= 0; i--) { final var entry = list.remove(i); @SuppressWarnings("unchecked") final T bagEntry = useWeakThreadLocals ? ((WeakReference) entry).get() : (T) entry; if (bagEntry != null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) { return bagEntry; } } // Otherwise, scan the shared list ... then poll the handoff queue final var waiting = waiters.incrementAndGet(); try { for (T bagEntry : sharedList) { if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) { // If we may have stolen another waiter's connection, request another bag add. if (waiting > 1) { listener.addBagItem(waiting - 1); } return bagEntry; } } listener.addBagItem(waiting); timeout = timeUnit.toNanos(timeout); do { final var start = currentTime(); final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS); if (bagEntry == null || bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) { return bagEntry; } timeout -= elapsedNanos(start); } while (timeout > 10_000); return null; } finally { waiters.decrementAndGet(); } } /** * This method will return a borrowed object to the bag. Objects * that are borrowed from the bag but never "requited" will result * in a memory leak. * * @param bagEntry the value to return to the bag * @throws NullPointerException if value is null * @throws IllegalStateException if the bagEntry was not borrowed from the bag */ public void requite(final T bagEntry) { bagEntry.setState(STATE_NOT_IN_USE); for (int i = 1, waiting = waiters.get(); waiting > 0; i++, waiting = waiters.get()) { if (bagEntry.getState() != STATE_NOT_IN_USE || handoffQueue.offer(bagEntry)) { return; } else if ((i & 0xff) == 0xff || (waiting > 1 && i % waiting == 0)) { parkNanos(MICROSECONDS.toNanos(10)); } else { Thread.yield(); } } final var threadLocalEntries = this.threadLocalList.get(); if (threadLocalEntries.size() < 16) { threadLocalEntries.add(useWeakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry); } } /** * Add a new object to the bag for others to borrow. * * @param bagEntry an object to add to the bag */ public void add(final T bagEntry) { if (closed) { LOGGER.info("ConcurrentBag has been closed, ignoring add()"); throw new IllegalStateException("ConcurrentBag has been closed, ignoring add()"); } sharedList.add(bagEntry); // spin until a thread takes it or none are waiting while (waiters.get() > 0 && bagEntry.getState() == STATE_NOT_IN_USE && !handoffQueue.offer(bagEntry)) { Thread.yield(); } } /** * Remove a value from the bag. This method should only be called * with objects obtained by borrow(long, TimeUnit) or reserve(T) * * @param bagEntry the value to remove * @return true if the entry was removed, false otherwise * @throws IllegalStateException if an attempt is made to remove an object * from the bag that was not borrowed or reserved first */ public boolean remove(final T bagEntry) { if (!bagEntry.compareAndSet(STATE_IN_USE, STATE_REMOVED) && !bagEntry.compareAndSet(STATE_RESERVED, STATE_REMOVED) && !closed) { LOGGER.warn("Attempt to remove an object from the bag that was not borrowed or reserved: {}", bagEntry); return false; } final var removed = sharedList.remove(bagEntry); if (!removed && !closed) { LOGGER.warn("Attempt to remove an object from the bag that does not exist: {}", bagEntry); } threadLocalList.get().remove(bagEntry); return removed; } /** * Close the bag to further adds. */ @Override public void close() { closed = true; } /** * This method provides a "snapshot" in time of the BagEntry * items in the bag in the specified state. It does not "lock" * or reserve items in any way. Call reserve(T) * on items in list before performing any action on them. * * @param state one of the {@link IConcurrentBagEntry} states * @return a possibly empty list of objects having the state specified */ public List values(final int state) { final var list = sharedList.stream().filter(e -> e.getState() == state).collect(Collectors.toList()); Collections.reverse(list); return list; } /** * This method provides a "snapshot" in time of the bag items. It * does not "lock" or reserve items in any way. Call reserve(T) * on items in the list, or understand the concurrency implications of * modifying items, before performing any action on them. * * @return a possibly empty list of (all) bag items */ @SuppressWarnings("unchecked") public List values() { return (List) sharedList.clone(); } /** * The method is used to make an item in the bag "unavailable" for * borrowing. It is primarily used when wanting to operate on items * returned by the values(int) method. Items that are * reserved can be removed from the bag via remove(T) * without the need to unreserve them. Items that are not removed * from the bag can be make available for borrowing again by calling * the unreserve(T) method. * * @param bagEntry the item to reserve * @return true if the item was able to be reserved, false otherwise */ public boolean reserve(final T bagEntry) { return bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_RESERVED); } /** * This method is used to make an item reserved via reserve(T) * available again for borrowing. * * @param bagEntry the item to unreserve */ @SuppressWarnings("SpellCheckingInspection") public void unreserve(final T bagEntry) { if (bagEntry.compareAndSet(STATE_RESERVED, STATE_NOT_IN_USE)) { // spin until a thread takes it or none are waiting for (int i = 1, waiting = waiters.get(); waiting > 0; i++, waiting = waiters.get()) { if (bagEntry.getState() != STATE_NOT_IN_USE || handoffQueue.offer(bagEntry)) { return; } else if ((i & 0xff) == 0xff || (waiting > 1 && i % waiting == 0)) { parkNanos(MICROSECONDS.toNanos(10)); } else { Thread.yield(); } } } else { LOGGER.warn("Attempt to relinquish an object to the bag that was not reserved: {}", bagEntry); } } /** * Get the number of threads pending (waiting) for an item from the * bag to become available. * * @return the number of threads waiting for items from the bag */ public int getWaitingThreadCount() { return waiters.get(); } /** * Get a count of the number of items in the specified state at the time of this call. * * @param state the state of the items to count * @return a count of how many items in the bag are in the specified state */ public int getCount(final int state) { var count = 0; for (var e : sharedList) { if (e.getState() == state) { count++; } } return count; } public int[] getStateCounts() { final var states = new int[6]; for (var e : sharedList) { ++states[e.getState()]; } states[4] = sharedList.size(); states[5] = waiters.get(); return states; } /** * Get the total number of items in the bag. * * @return the number of items in the bag */ public int size() { return sharedList.size(); } public void dumpState() { sharedList.forEach(entry -> LOGGER.info(entry.toString())); } /** * Determine whether to use WeakReferences based on whether there is a * custom ClassLoader implementation sitting between this class and the * System ClassLoader. * * @return true if we should use WeakReferences in our ThreadLocals, false otherwise */ private boolean useWeakThreadLocals() { try { if (System.getProperty("com.zaxxer.hikari.useWeakReferences") != null) { // undocumented manual override of WeakReference behavior return Boolean.getBoolean("com.zaxxer.hikari.useWeakReferences"); } return getClass().getClassLoader() != ClassLoader.getSystemClassLoader(); } catch (SecurityException se) { return true; } } } ================================================ FILE: src/main/java/com/zaxxer/hikari/util/Credentials.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.util; import javax.management.ConstructorParameters; /** * A simple class to hold connection credentials and is designed to be immutable. */ public final class Credentials { private final String username; private final String password; /** * Construct an immutable Credentials object with the supplied username and password. * * @param username the username * @param password the password * @return a new Credentials object */ public static Credentials of(final String username, final String password) { return new Credentials(username, password); } /** * Construct an immutable Credentials object with the supplied username and password. * * @param username the username * @param password the password */ @ConstructorParameters({ "username", "password" }) public Credentials(final String username, final String password) { this.username = username; this.password = password; } /** * Get the username. * * @return the username */ public String getUsername() { return username; } /** * Get the password. * * @return the password */ public String getPassword() { return password; } } ================================================ FILE: src/main/java/com/zaxxer/hikari/util/DriverDataSource.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.util; import java.io.PrintWriter; import java.sql.Connection; import java.sql.Driver; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.Properties; import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static com.zaxxer.hikari.util.UtilityElf.maskPasswordInJdbcUrl; /** * A DataSource implementation that uses a JDBC Driver to create connections. * This class is used to provide a DataSource that can be configured with a * specific JDBC driver and connection properties. * @author Brett Wooldridge * @hidden */ public final class DriverDataSource implements DataSource { private static final Logger LOGGER = LoggerFactory.getLogger(DriverDataSource.class); private static final String PASSWORD = "password"; private static final String USER = "user"; private final String jdbcUrl; private final Properties driverProperties; private Driver driver; public DriverDataSource(String jdbcUrl, String driverClassName, Properties properties, String username, String password) { this.jdbcUrl = jdbcUrl; this.driverProperties = new Properties(); driverProperties.putAll(properties); if (username != null) { driverProperties.put(USER, driverProperties.getProperty(USER, username)); } if (password != null) { driverProperties.put(PASSWORD, driverProperties.getProperty(PASSWORD, password)); } if (driverClassName != null) { var drivers = DriverManager.getDrivers(); while (drivers.hasMoreElements()) { var d = drivers.nextElement(); if (d.getClass().getName().equals(driverClassName)) { driver = d; break; } } if (driver == null) { LOGGER.warn("Registered driver with driverClassName={} was not found, trying direct instantiation.", driverClassName); Class driverClass = null; var threadContextClassLoader = Thread.currentThread().getContextClassLoader(); try { if (threadContextClassLoader != null) { try { driverClass = threadContextClassLoader.loadClass(driverClassName); LOGGER.debug("Driver class {} found in Thread context class loader {}", driverClassName, threadContextClassLoader); } catch (ClassNotFoundException e) { LOGGER.debug("Driver class {} not found in Thread context class loader {}, trying classloader {}", driverClassName, threadContextClassLoader, this.getClass().getClassLoader()); } } if (driverClass == null) { driverClass = this.getClass().getClassLoader().loadClass(driverClassName); LOGGER.debug("Driver class {} found in the HikariConfig class classloader {}", driverClassName, this.getClass().getClassLoader()); } } catch (ClassNotFoundException e) { LOGGER.debug("Failed to load driver class {} from HikariConfig class classloader {}", driverClassName, this.getClass().getClassLoader()); } if (driverClass != null) { try { driver = (Driver) driverClass.getDeclaredConstructor().newInstance(); } catch (Exception e) { LOGGER.warn("Failed to create instance of driver class {}, trying jdbcUrl resolution", driverClassName, e); } } } } final var sanitizedUrl = maskPasswordInJdbcUrl(jdbcUrl); try { if (driver == null) { driver = DriverManager.getDriver(jdbcUrl); LOGGER.debug("Loaded driver with class name {} for jdbcUrl={}", driver.getClass().getName(), sanitizedUrl); } else if (!driver.acceptsURL(jdbcUrl)) { throw new RuntimeException("Driver " + driverClassName + " claims to not accept jdbcUrl, " + sanitizedUrl); } } catch (SQLException e) { throw new RuntimeException("Failed to get driver instance for jdbcUrl=" + sanitizedUrl, e); } } @Override public Connection getConnection() throws SQLException { return driver.connect(jdbcUrl, driverProperties); } @Override public Connection getConnection(final String username, final String password) throws SQLException { final var cloned = (Properties) driverProperties.clone(); if (username != null) { cloned.put(USER, username); if (cloned.containsKey("username")) { cloned.put("username", username); } } if (password != null) { cloned.put(PASSWORD, password); } return driver.connect(jdbcUrl, cloned); } @Override public PrintWriter getLogWriter() throws SQLException { throw new SQLFeatureNotSupportedException(); } @Override public void setLogWriter(PrintWriter logWriter) throws SQLException { throw new SQLFeatureNotSupportedException(); } @Override public void setLoginTimeout(int seconds) throws SQLException { DriverManager.setLoginTimeout(seconds); } @Override public int getLoginTimeout() throws SQLException { return DriverManager.getLoginTimeout(); } @Override public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException { return driver.getParentLogger(); } @Override public T unwrap(Class iface) throws SQLException { throw new SQLFeatureNotSupportedException(); } @Override public boolean isWrapperFor(Class iface) throws SQLException { return false; } } ================================================ FILE: src/main/java/com/zaxxer/hikari/util/FastList.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.util; import java.io.Serializable; import java.lang.reflect.Array; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; import java.util.RandomAccess; import java.util.Spliterator; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; /** * Fast list without range checking. * * @author Brett Wooldridge * @hidden */ @SuppressWarnings("NullableProblems") public final class FastList implements List, RandomAccess, Serializable { private static final long serialVersionUID = -4598088075242913858L; private final Class clazz; private T[] elementData; private int size; /** * Construct a FastList with a default size of 32. * @param clazz the Class stored in the collection */ @SuppressWarnings("unchecked") public FastList(Class clazz) { this.elementData = (T[]) Array.newInstance(clazz, 32); this.clazz = clazz; } /** * Construct a FastList with a specified size. * @param clazz the Class stored in the collection * @param capacity the initial size of the FastList */ @SuppressWarnings("unchecked") public FastList(Class clazz, int capacity) { this.elementData = (T[]) Array.newInstance(clazz, capacity); this.clazz = clazz; } /** * Add an element to the tail of the FastList. * * @param element the element to add */ @Override public boolean add(T element) { if (size < elementData.length) { elementData[size++] = element; } else { // overflow-conscious code final var oldCapacity = elementData.length; final var newCapacity = oldCapacity << 1; @SuppressWarnings("unchecked") final var newElementData = (T[]) Array.newInstance(clazz, newCapacity); System.arraycopy(elementData, 0, newElementData, 0, oldCapacity); newElementData[size++] = element; elementData = newElementData; } return true; } /** * Get the element at the specified index. * * @param index the index of the element to get * @return the element, or ArrayIndexOutOfBounds is thrown if the index is invalid */ @Override public T get(int index) { return elementData[index]; } /** * Remove the last element from the list. No bound check is performed, so if this * method is called on an empty list and ArrayIndexOutOfBounds exception will be * thrown. * * @return the last element of the list */ public T removeLast() { T element = elementData[--size]; elementData[size] = null; return element; } /** * This remove method is most efficient when the element being removed * is the last element. Equality is identity based, not equals() based. * Only the first matching element is removed. * * @param element the element to remove */ @Override public boolean remove(Object element) { for (var index = size - 1; index >= 0; index--) { if (element == elementData[index]) { final var numMoved = size - index - 1; if (numMoved > 0) { System.arraycopy(elementData, index + 1, elementData, index, numMoved); } elementData[--size] = null; return true; } } return false; } /** * Clear the FastList. */ @Override public void clear() { for (var i = 0; i < size; i++) { elementData[i] = null; } size = 0; } /** * Get the current number of elements in the FastList. * * @return the number of current elements */ @Override public int size() { return size; } /** {@inheritDoc} */ @Override public boolean isEmpty() { return size == 0; } /** {@inheritDoc} */ @Override public T set(int index, T element) { T old = elementData[index]; elementData[index] = element; return old; } /** {@inheritDoc} */ @Override public T remove(int index) { if (size == 0) { return null; } final T old = elementData[index]; final var numMoved = size - index - 1; if (numMoved > 0) { System.arraycopy(elementData, index + 1, elementData, index, numMoved); } elementData[--size] = null; return old; } /** {@inheritDoc} */ @Override public boolean contains(Object o) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public Iterator iterator() { return new Iterator<>() { private int index; @Override public boolean hasNext() { return index < size; } @Override public T next() { if (index < size) { return elementData[index++]; } throw new NoSuchElementException("No more elements in FastList"); } }; } /** {@inheritDoc} */ @Override public Object[] toArray() { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public E[] toArray(E[] a) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public boolean containsAll(Collection c) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public boolean addAll(int index, Collection c) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public void add(int index, T element) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public int indexOf(Object o) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public int lastIndexOf(Object o) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public ListIterator listIterator() { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public ListIterator listIterator(int index) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public List subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public Object clone() { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public void forEach(Consumer action) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public Spliterator spliterator() { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public boolean removeIf(Predicate filter) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public void replaceAll(UnaryOperator operator) { throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public void sort(Comparator c) { throw new UnsupportedOperationException(); } } ================================================ FILE: src/main/java/com/zaxxer/hikari/util/IsolationLevel.java ================================================ /* * Copyright (C) 2019 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; /** * An enumeration representing the various isolation levels for database transactions. * Each isolation level corresponds to a specific behavior regarding visibility of changes made by other transactions. * The levels are defined according to the SQL standard and may vary in implementation across different databases. * * @author Brett Wooldridge * @hidden */ public enum IsolationLevel { TRANSACTION_NONE(0), TRANSACTION_READ_UNCOMMITTED(1), TRANSACTION_READ_COMMITTED(2), TRANSACTION_CURSOR_STABILITY(3), TRANSACTION_REPEATABLE_READ(4), TRANSACTION_LAST_COMMITTED(5), TRANSACTION_SERIALIZABLE(8), TRANSACTION_SQL_SERVER_SNAPSHOT_ISOLATION_LEVEL(4096); private final int levelId; IsolationLevel(int levelId) { this.levelId = levelId; } public int getLevelId() { return levelId; } } ================================================ FILE: src/main/java/com/zaxxer/hikari/util/JavassistProxyFactory.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.util; import com.zaxxer.hikari.pool.*; import javassist.*; import javassist.bytecode.ClassFile; import java.io.File; import java.io.IOException; import java.lang.reflect.Array; import java.sql.*; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; /** * This class generates the proxy objects for {@link Connection}, {@link Statement}, * {@link PreparedStatement}, and {@link CallableStatement}. Additionally it injects * method bodies into the {@link ProxyFactory} class methods that can instantiate * instances of the generated proxies. * * @author Brett Wooldridge * @author Yanming Zhou * @hidden */ public final class JavassistProxyFactory { private static ClassPool classPool; private static String genDirectory = "target" + File.separator + "classes"; public static void main(String... args) throws Exception { classPool = new ClassPool(); classPool.importPackage("java.sql"); classPool.appendClassPath(new LoaderClassPath(JavassistProxyFactory.class.getClassLoader())); if (args.length > 0) { String parentDir = args[0]; if (!parentDir.endsWith(File.separator)) { parentDir += File.separator; } genDirectory = parentDir + genDirectory; } System.out.println("Generating following classes to " + genDirectory); // Cast is not needed for these String methodBody = "{ try { return delegate.method($$); } catch (SQLException e) { throw checkException(e); } }"; generateProxyClass(Connection.class, ProxyConnection.class.getName(), methodBody); generateProxyClass(Statement.class, ProxyStatement.class.getName(), methodBody); generateProxyClass(ResultSet.class, ProxyResultSet.class.getName(), methodBody); generateProxyClass(DatabaseMetaData.class, ProxyDatabaseMetaData.class.getName(), methodBody); // For these we have to cast the delegate methodBody = "{ try { return ((cast) delegate).method($$); } catch (SQLException e) { throw checkException(e); } }"; generateProxyClass(PreparedStatement.class, ProxyPreparedStatement.class.getName(), methodBody); generateProxyClass(CallableStatement.class, ProxyCallableStatement.class.getName(), methodBody); modifyProxyFactory(); } private static void modifyProxyFactory() throws NotFoundException, CannotCompileException, IOException { System.out.println("Generating method bodies for com.zaxxer.hikari.proxy.ProxyFactory"); var packageName = ProxyConnection.class.getPackage().getName(); var proxyCt = classPool.getCtClass("com.zaxxer.hikari.pool.ProxyFactory"); for (var method : proxyCt.getMethods()) { switch (method.getName()) { case "getProxyConnection": method.setBody("{return new " + packageName + ".HikariProxyConnection($$);}"); break; case "getProxyStatement": method.setBody("{return new " + packageName + ".HikariProxyStatement($$);}"); break; case "getProxyPreparedStatement": method.setBody("{return new " + packageName + ".HikariProxyPreparedStatement($$);}"); break; case "getProxyCallableStatement": method.setBody("{return new " + packageName + ".HikariProxyCallableStatement($$);}"); break; case "getProxyResultSet": method.setBody("{return new " + packageName + ".HikariProxyResultSet($$);}"); break; case "getProxyDatabaseMetaData": method.setBody("{return new " + packageName + ".HikariProxyDatabaseMetaData($$);}"); break; default: // unhandled method break; } } proxyCt.writeFile(genDirectory); } /** * Generate Javassist Proxy Classes */ private static void generateProxyClass(Class primaryInterface, String superClassName, String methodBody) throws Exception { var newClassName = superClassName.replaceAll("(.+)\\.(\\w+)", "$1.Hikari$2"); var superCt = classPool.getCtClass(superClassName); var targetCt = classPool.makeClass(newClassName, superCt); targetCt.setModifiers(Modifier.setPublic(Modifier.FINAL)); System.out.println("Generating " + newClassName); // Make a set of method signatures we inherit implementation for, so we don't generate delegates for these var superSigs = new HashSet(); for (var method : superCt.getMethods()) { if ((method.getModifiers() & Modifier.FINAL) == Modifier.FINAL) { superSigs.add(method.getName() + method.getSignature()); } } var methods = new HashSet(); for (var intf : getAllInterfaces(primaryInterface)) { var intfCt = classPool.getCtClass(intf.getName()); targetCt.addInterface(intfCt); for (var intfMethod : intfCt.getDeclaredMethods()) { final var signature = intfMethod.getName() + intfMethod.getSignature(); // don't generate delegates for methods we override if (superSigs.contains(signature)) { continue; } // Ignore already added methods that come from other interfaces if (methods.contains(signature)) { continue; } // Track what methods we've added methods.add(signature); // Clone the method we want to inject into var method = CtNewMethod.copy(intfMethod, targetCt, null); var modifiedBody = methodBody; // If the super-Proxy has concrete methods (non-abstract), transform the call into a simple super.method() call var superMethod = superCt.getMethod(intfMethod.getName(), intfMethod.getSignature()); if ((superMethod.getModifiers() & Modifier.ABSTRACT) != Modifier.ABSTRACT && !isDefaultMethod(intf, intfMethod)) { modifiedBody = modifiedBody.replace("((cast) ", ""); modifiedBody = modifiedBody.replace("delegate", "super"); modifiedBody = modifiedBody.replace("super)", "super"); } modifiedBody = modifiedBody.replace("cast", primaryInterface.getName()); // Generate a method that simply invokes the same method on the delegate if (isThrowsSqlException(intfMethod)) { modifiedBody = modifiedBody.replace("method", method.getName()); } else { modifiedBody = "{ return ((cast) delegate).method($$); }".replace("method", method.getName()).replace("cast", primaryInterface.getName()); } if (method.getReturnType() == CtClass.voidType) { modifiedBody = modifiedBody.replace("return", ""); } method.setBody(modifiedBody); targetCt.addMethod(method); } } targetCt.getClassFile().setMajorVersion(ClassFile.JAVA_8); targetCt.writeFile(genDirectory); } private static boolean isThrowsSqlException(CtMethod method) { try { for (var clazz : method.getExceptionTypes()) { if (clazz.getSimpleName().equals("SQLException")) { return true; } } } catch (NotFoundException e) { // fall thru } return false; } private static boolean isDefaultMethod(Class intf, CtMethod intfMethod) throws Exception { var paramTypes = new ArrayList>(); for (var pt : intfMethod.getParameterTypes()) { paramTypes.add(toJavaClass(pt)); } return intf.getDeclaredMethod(intfMethod.getName(), paramTypes.toArray(new Class[0])).toString().contains("default "); } private static Set> getAllInterfaces(Class clazz) { var interfaces = new LinkedHashSet>(); for (var intf : clazz.getInterfaces()) { if (intf.getInterfaces().length > 0) { interfaces.addAll(getAllInterfaces(intf)); } interfaces.add(intf); } if (clazz.getSuperclass() != null) { interfaces.addAll(getAllInterfaces(clazz.getSuperclass())); } if (clazz.isInterface()) { interfaces.add(clazz); } return interfaces; } private static Class toJavaClass(CtClass cls) throws Exception { if (cls.getName().endsWith("[]")) { return Array.newInstance(toJavaClass(cls.getName().replace("[]", "")), 0).getClass(); } else { return toJavaClass(cls.getName()); } } private static Class toJavaClass(String cn) throws Exception { switch (cn) { case "int": return int.class; case "long": return long.class; case "short": return short.class; case "byte": return byte.class; case "float": return float.class; case "double": return double.class; case "boolean": return boolean.class; case "char": return char.class; case "void": return void.class; default: return Class.forName(cn); } } } ================================================ FILE: src/main/java/com/zaxxer/hikari/util/PropertyElf.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.util; import com.zaxxer.hikari.HikariConfig; import org.slf4j.LoggerFactory; import java.lang.reflect.Method; import java.time.Duration; import java.util.*; import java.util.regex.Pattern; /** * A class that reflectively sets bean properties on a target object. * * @author Brett Wooldridge * @hidden */ public final class PropertyElf { private static final char ESCAPE_CHAR = '\\'; private static final char SEPARATOR_CHAR = ','; private static final Pattern DURATION_PATTERN = Pattern.compile("^(?\\d+)(?ms|s|m|h|d)$"); private PropertyElf() { // cannot be constructed } public static void setTargetFromProperties(final Object target, final Properties properties) { if (target == null || properties == null) { return; } var methods = Arrays.asList(target.getClass().getMethods()); properties.forEach((key, value) -> { var keyName = key.toString(); if (target instanceof HikariConfig && keyName.startsWith("dataSource.")) { ((HikariConfig) target).addDataSourceProperty(keyName.substring("dataSource.".length()), value); } else { setProperty(target, keyName, value, methods); } }); } /** * Get the bean-style property names for the specified object. * * @param targetClass the target object * @return a set of property names */ public static Set getPropertyNames(final Class targetClass) { var set = new HashSet(); for (var method : targetClass.getMethods()) { var name = propertyNameFromGetterName(method.getName()); try { if (method.getParameterTypes().length == 0 && name != null) { targetClass.getMethod("set" + capitalizedPropertyName(name), method.getReturnType()); // throws if method setter does not exist set.add(name); } } catch (Exception e) { // fall thru (continue) } } return set; } public static Object getProperty(final String propName, final Object target) { try { // use the english locale to avoid the infamous turkish locale bug var capitalized = "get" + capitalizedPropertyName(propName); var method = target.getClass().getMethod(capitalized); return method.invoke(target); } catch (Exception e) { try { var capitalized = "is" + capitalizedPropertyName(propName); var method = target.getClass().getMethod(capitalized); return method.invoke(target); } catch (Exception e2) { return null; } } } public static Properties copyProperties(final Properties props) { var copy = new Properties(); props.forEach((key, value) -> copy.setProperty(key.toString(), value.toString())); return copy; } private static String propertyNameFromGetterName(final String methodName) { String name = null; if (methodName.startsWith("get") && methodName.length() > 3) { name = methodName.substring(3); } else if (methodName.startsWith("is") && methodName.length() > 2) { name = methodName.substring(2); } if (name != null) { return Character.toLowerCase(name.charAt(0)) + name.substring(1); } return null; } private static void setProperty(final Object target, final String propName, final Object propValue, final List methods) { final var logger = LoggerFactory.getLogger(PropertyElf.class); // use the english locale to avoid the infamous turkish locale bug var methodName = "set" + propName.substring(0, 1).toUpperCase(Locale.ENGLISH) + propName.substring(1); var writeMethod = methods.stream().filter(m -> m.getName().equals(methodName) && m.getParameterCount() == 1).findFirst().orElse(null); if (writeMethod == null) { var methodName2 = "set" + propName.toUpperCase(Locale.ENGLISH); writeMethod = methods.stream().filter(m -> m.getName().equals(methodName2) && m.getParameterCount() == 1).findFirst().orElse(null); } if (writeMethod == null) { logger.error("Property {} does not exist on target {}", propName, target.getClass()); throw new RuntimeException(String.format("Property %s does not exist on target %s", propName, target.getClass())); } try { var paramClass = writeMethod.getParameterTypes()[0]; String value = propValue.toString(); if (paramClass == int.class) { writeMethod.invoke(target, Integer.parseInt(propValue.toString())); } else if (paramClass == long.class) { writeMethod.invoke(target, parseDuration(value).map(Duration::toMillis).orElseGet(() -> Long.parseLong(value))); } else if (paramClass == short.class) { writeMethod.invoke(target, Short.parseShort(value)); } else if (paramClass == boolean.class || paramClass == Boolean.class) { writeMethod.invoke(target, Boolean.parseBoolean(value)); } else if (paramClass.isArray() && char.class.isAssignableFrom(paramClass.getComponentType())) { writeMethod.invoke(target, value.toCharArray()); } else if (paramClass.isArray() && int.class.isAssignableFrom(paramClass.getComponentType())) { writeMethod.invoke(target, parseIntArray(value)); } else if (paramClass.isArray() && String.class.isAssignableFrom(paramClass.getComponentType())) { writeMethod.invoke(target, new Object[]{parseStringArray(value)}); } else if (paramClass == String.class) { writeMethod.invoke(target, value); } else { try { logger.debug("Try to create a new instance of \"{}\"", propValue); writeMethod.invoke(target, Class.forName(propValue.toString()).getDeclaredConstructor().newInstance()); } catch (InstantiationException | ClassNotFoundException e) { logger.debug("Class \"{}\" not found or could not instantiate it (Default constructor)", propValue); writeMethod.invoke(target, propValue); } } } catch (Exception e) { logger.error("Failed to set property {} on target {}", propName, target.getClass(), e); throw new RuntimeException(e); } } private static String capitalizedPropertyName(String propertyName) { // use the english locale to avoid the infamous turkish locale bug return propertyName.substring(0, 1).toUpperCase(Locale.ENGLISH) + propertyName.substring(1); } private static int[] parseIntArray(String value) { if (value == null || value.isEmpty() ) { return new int[0]; } var split = value.split(","); var intArray = new int[split.length]; for (int i = 0; i < split.length; i++) { intArray[i] = Integer.parseInt(split[i]); } return intArray; } private static String[] parseStringArray(String value) { if (value == null || value.isEmpty()) { return new String[0]; } var resultList = new ArrayList(); var inEscape = false; var currentField = new StringBuilder(); for (var c : value.toCharArray()) { if (inEscape) { currentField.append(c); inEscape = false; } else if (c == ESCAPE_CHAR) { inEscape = true; } else if (c == SEPARATOR_CHAR) { resultList.add(currentField.toString()); currentField.setLength(0); } else { currentField.append(c); } } if (inEscape) { throw new IllegalArgumentException(String.format("Unterminated escape sequence in property value: %s", value)); } resultList.add(currentField.toString()); return resultList.toArray(new String[0]); } private static Optional parseDuration(String value) { var matcher = DURATION_PATTERN.matcher(value); if (matcher.matches()) { var number = Long.parseLong(matcher.group("number")); var unit = matcher.group("unit"); switch (unit) { case "ms": return Optional.of(Duration.ofMillis(number)); case "s": return Optional.of(Duration.ofSeconds(number)); case "m": return Optional.of(Duration.ofMinutes(number)); case "h": return Optional.of(Duration.ofHours(number)); case "d": return Optional.of(Duration.ofDays(number)); default: throw new IllegalStateException(String.format("Could not match unit, got %s (from given value %s)", unit, value)); } } else { return Optional.empty(); } } } ================================================ FILE: src/main/java/com/zaxxer/hikari/util/SuspendResumeLock.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.util; import java.sql.SQLException; import java.sql.SQLTransientException; import java.util.concurrent.Semaphore; /** * This class implements a lock that can be used to suspend and resume the pool. It * also provides a faux implementation that is used when the feature is disabled that * hopefully gets fully "optimized away" by the JIT. * * @author Brett Wooldridge * @hidden */ public class SuspendResumeLock { public static final SuspendResumeLock FAUX_LOCK = new SuspendResumeLock(false) { @Override public void acquire() {} @Override public void release() {} @Override public void suspend() {} @Override public void resume() {} }; private static final int MAX_PERMITS = 10000; private final Semaphore acquisitionSemaphore; /** * Default constructor */ public SuspendResumeLock() { this(true); } private SuspendResumeLock(final boolean createSemaphore) { acquisitionSemaphore = (createSemaphore ? new Semaphore(MAX_PERMITS, true) : null); } public void acquire() throws SQLException { if (acquisitionSemaphore.tryAcquire()) { return; } else if (Boolean.getBoolean("com.zaxxer.hikari.throwIfSuspended")) { throw new SQLTransientException("The pool is currently suspended and configured to throw exceptions upon acquisition"); } acquisitionSemaphore.acquireUninterruptibly(); } public void release() { acquisitionSemaphore.release(); } public void suspend() { acquisitionSemaphore.acquireUninterruptibly(MAX_PERMITS); } public void resume() { acquisitionSemaphore.release(MAX_PERMITS); } } ================================================ FILE: src/main/java/com/zaxxer/hikari/util/UtilityElf.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Constructor; import java.util.Arrays; import java.util.Locale; import java.util.concurrent.*; import java.util.regex.Pattern; import java.util.stream.IntStream; import static java.lang.Thread.currentThread; import static java.util.concurrent.TimeUnit.SECONDS; /** * UtilityElf is a utility class that provides various helper methods * for string manipulation, thread management, and JDBC URL handling. * It includes methods for masking passwords in JDBC URLs, creating * instances of classes, and creating thread pool executors. * * @author Brett Wooldridge * @hidden */ public final class UtilityElf { private static final Logger LOGGER = LoggerFactory.getLogger(UtilityElf.class); /** * A pattern to match and mask passwords in JDBC URLs. * It looks for the "password" parameter in the URL and replaces its value with "". */ private static final Pattern PASSWORD_MASKING_PATTERN = Pattern.compile("([?&;][^&#;=]*[pP]assword=)[^&#;]*"); private UtilityElf() { // non-constructable } public static String maskPasswordInJdbcUrl(String jdbcUrl) { return PASSWORD_MASKING_PATTERN.matcher(jdbcUrl).replaceAll("$1"); } /** * Get a trimmed string or null if the string is null or empty. * * @param text the string to check * @return null if string is null or empty, trimmed string otherwise */ public static String getNullIfEmpty(final String text) { return text == null ? null : text.trim().isEmpty() ? null : text.trim(); } /** * Sleep and suppress InterruptedException (but re-signal it). * * @param millis the number of milliseconds to sleep */ public static void quietlySleep(final long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { // I said be quiet! currentThread().interrupt(); } } /** * Checks whether an object is an instance of given type without throwing exception when the class is not loaded. * @param obj the object to check * @param className String class * @return true if object is assignable from the type, false otherwise or when the class cannot be loaded */ public static boolean safeIsAssignableFrom(Object obj, String className) { try { var clazz = Class.forName(className); return clazz.isAssignableFrom(obj.getClass()); } catch (ClassNotFoundException ignored) { return false; } } public static T createInstance(final String className, final Class clazz) { return createInstance(className, clazz, new Object[0]); } /** * Create and instance of the specified class using the constructor matching the specified * arguments. * * @param the class type * @param className the name of the class to instantiate * @param clazz a class to cast the result as * @param args arguments to a constructor * @return an instance of the specified class */ public static T createInstance(final String className, final Class clazz, final Object... args) { if (className == null) { return null; } try { var loaded = attemptFromContextLoader(className); if (loaded == null) { loaded = UtilityElf.class.getClassLoader().loadClass(className); LOGGER.debug("Class {} loaded from classloader {}", className, UtilityElf.class.getClassLoader()); } var totalArgs = args.length; if (totalArgs == 0) { return clazz.cast(loaded.getDeclaredConstructor().newInstance()); } var argClasses = new Class[totalArgs]; for (int i = 0; i < totalArgs; i++) { argClasses[i] = args[i].getClass(); } Constructor constructor = Arrays.stream(loaded.getConstructors()) .filter(c -> { if (c.getParameterCount() != totalArgs) return false; Class[] params = c.getParameterTypes(); return IntStream.range(0, totalArgs) .allMatch(i -> params[i].isAssignableFrom(argClasses[i])); }) .findFirst() .orElseThrow(() -> new RuntimeException("No suitable constructor found for class " + className + " with arguments " + Arrays.toString(args))); return clazz.cast(constructor.newInstance(args)); } catch (Exception e) { throw new RuntimeException("Failed to load class " + className, e); } } /** * Create a ThreadPoolExecutor. * * @param queueSize the queue size * @param threadName the thread name * @param threadFactory an optional ThreadFactory * @param policy the RejectedExecutionHandler policy * @return a ThreadPoolExecutor */ public static ThreadPoolExecutor createThreadPoolExecutor(final int queueSize, final String threadName, ThreadFactory threadFactory, final RejectedExecutionHandler policy) { return createThreadPoolExecutor(new LinkedBlockingQueue<>(queueSize), threadName, threadFactory, policy); } /** * Create a ThreadPoolExecutor. * * @param queue the BlockingQueue to use * @param threadName the thread name * @param threadFactory an optional ThreadFactory * @param policy the RejectedExecutionHandler policy * @return a ThreadPoolExecutor */ public static ThreadPoolExecutor createThreadPoolExecutor(final BlockingQueue queue, final String threadName, ThreadFactory threadFactory, final RejectedExecutionHandler policy) { if (threadFactory == null) { threadFactory = new DefaultThreadFactory(threadName); } var executor = new ThreadPoolExecutor(1 /*core*/, 1 /*max*/, 5 /*keepalive*/, SECONDS, queue, threadFactory, policy); executor.allowCoreThreadTimeOut(true); return executor; } // *********************************************************************** // Misc. public methods // *********************************************************************** /** * Get the int value of a transaction isolation level by name. * * @param transactionIsolationName the name of the transaction isolation level * @return the int value of the isolation level or -1 */ public static int getTransactionIsolation(final String transactionIsolationName) { if (transactionIsolationName != null) { try { // use the english locale to avoid the infamous turkish locale bug final var upperCaseIsolationLevelName = transactionIsolationName.toUpperCase(Locale.ENGLISH); return IsolationLevel.valueOf(upperCaseIsolationLevelName).getLevelId(); } catch (IllegalArgumentException e) { // legacy support for passing an integer version of the isolation level try { final var level = Integer.parseInt(transactionIsolationName); for (var iso : IsolationLevel.values()) { if (iso.getLevelId() == level) { return iso.getLevelId(); } } throw new IllegalArgumentException("Invalid transaction isolation value: " + transactionIsolationName); } catch (NumberFormatException nfe) { throw new IllegalArgumentException("Invalid transaction isolation value: " + transactionIsolationName, nfe); } } } return -1; } /** * Custom RejectedExecutionHandler that does nothing when a task is rejected. * * @see java.util.concurrent.RejectedExecutionHandler * @see java.util.concurrent.ThreadPoolExecutor * @hidden */ public static class CustomDiscardPolicy implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { } } /** * Default ThreadFactory implementation that creates daemon threads with a specified name. * * @see java.util.concurrent.ThreadFactory * @hidden */ public static final class DefaultThreadFactory implements ThreadFactory { private final String threadName; private final boolean daemon; public DefaultThreadFactory(String threadName) { this.threadName = threadName; this.daemon = true; } @Override @SuppressWarnings("NullableProblems") public Thread newThread(Runnable r) { var thread = new Thread(r, threadName); thread.setDaemon(daemon); return thread; } } // *********************************************************************** // Private methods // *********************************************************************** private static Class attemptFromContextLoader(final String className) { final var threadContextClassLoader = Thread.currentThread().getContextClassLoader(); if (threadContextClassLoader != null) { try { final var clazz = threadContextClassLoader.loadClass(className); LOGGER.debug("Class {} found in Thread context class loader {}", className, threadContextClassLoader); return clazz; } catch (ClassNotFoundException e) { LOGGER.debug("Class {} not found in Thread context class loader {}, trying classloader {}", className, threadContextClassLoader, UtilityElf.class.getClassLoader()); } } return null; } } ================================================ FILE: src/main/java/module-info.java ================================================ module com.zaxxer.hikari { requires java.sql; requires java.management; requires java.naming; requires org.slf4j; requires static org.hibernate.orm.core; requires static simpleclient; requires static metrics.core; requires static metrics.healthchecks; requires static io.dropwizard.metrics5; requires static micrometer.core; requires static org.javassist; exports com.zaxxer.hikari; exports com.zaxxer.hikari.hibernate; exports com.zaxxer.hikari.metrics; exports com.zaxxer.hikari.metrics.dropwizard; exports com.zaxxer.hikari.metrics.micrometer; exports com.zaxxer.hikari.metrics.prometheus; exports com.zaxxer.hikari.pool; exports com.zaxxer.hikari.util; } ================================================ FILE: src/test/java/com/zaxxer/hikari/HikariConfigTest.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.config.Property; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.Arrays; import java.util.List; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static org.junit.Assert.*; public class HikariConfigTest { private TestAppender testAppender = new TestAppender(); @Before public void setup() { getLoggerConfig().addAppender(testAppender, Level.ALL, null); } @After public void tearDown() { getLoggerConfig().removeAppender(testAppender.getName()); } private static LoggerConfig getLoggerConfig() { LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false); Configuration configuration = loggerContext.getConfiguration(); return configuration.getRootLogger(); } @Test public void testJdbcUrlLogging() { List urls = Arrays.asList( "jdbc:postgresql://host/d_dlq?user=USER&password=SECRET", "jdbc:postgresql://host/d_dlq?user=USER&truststorePassword=SECRET", "jdbc:postgresql://host/d_dlq?a=b&password=SECRET&user=USER", "jdbc:postgresql://host/d_dlq?a=b&sslpassword=SECRET&user=USER", "jdbc:postgresql://host/d_dlq?truststorePassword=SECRET;user=USER&password=SECRET#extra", "jdbc:postgresql://host/d_dlq?a=b&sslpassword=SECRET&password=SECRET&user=USER", "jdbc:postgresql://host/d_dlq?sslpassword=SECRET&password=SECRET&trustPassword=SECRET&user=USER", "jdbc:postgresql://host/d_dlq?password=SECRET#user=USER;extra" ); for (String url : urls) { testJdbcUrl(url); } } private void testJdbcUrl(String jdbcUrl) { HikariConfig config = newHikariConfig(); config.setJdbcUrl(jdbcUrl); config.validate(); assertTrue(testAppender.getLog().contains("jdbc:postgresql://host/d_dlq")); assertTrue(testAppender.getLog().contains("user=USER")); assertFalse("Log should not contain password", testAppender.getLog().contains("SECRET")); } private static class TestAppender extends AbstractAppender { private String log; TestAppender() { super("TestAppender", (Filter)null, (Layout)null, true, Property.EMPTY_ARRAY); } @Override public void append(LogEvent event) { log += event.getMessage().getFormattedMessage() + "\n"; } String getLog() { return log; } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/datasource/TestSealedConfig.java ================================================ /* * 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.datasource; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.util.Credentials; import org.junit.Test; import java.sql.Connection; import java.sql.SQLException; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static org.junit.Assert.fail; public class TestSealedConfig { @Test(expected = IllegalStateException.class) public void testSealed1() throws SQLException { HikariConfig config = newHikariConfig(); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); fail("Exception should have been thrown"); } } @Test(expected = IllegalStateException.class) public void testSealed2() throws SQLException { HikariDataSource ds = new HikariDataSource(); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource closeable = ds) { try (Connection connection = ds.getConnection()) { ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); fail("Exception should have been thrown"); } } } @Test(expected = IllegalStateException.class) public void testSealed3() throws SQLException { HikariDataSource ds = new HikariDataSource(); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource closeable = ds) { try (Connection connection = ds.getConnection()) { ds.setAutoCommit(false); fail("Exception should have been thrown"); } } } @Test public void testSealedAccessibleMethods() throws SQLException { HikariConfig config = newHikariConfig(); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { ds.setConnectionTimeout(5000); ds.setValidationTimeout(5000); ds.setIdleTimeout(30000); ds.setLeakDetectionThreshold(60000); ds.setMaxLifetime(1800000); ds.setMinimumIdle(5); ds.setMaximumPoolSize(8); ds.setPassword("password"); ds.setUsername("username"); ds.setCredentials(Credentials.of("anothername", "anotherpassword")); } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/db/BasicPoolTest.java ================================================ /* * Copyright (C) 2016 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.db; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.pool.HikariPool; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import static com.zaxxer.hikari.pool.TestElf.getPool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.pool.TestElf.getUnsealedConfig; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; /** * @author brettw * */ public class BasicPoolTest { @Before public void setup() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(2); config.setConnectionTestQuery("SELECT 1"); config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource"); config.addDataSourceProperty("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"); try (HikariDataSource ds = new HikariDataSource(config); Connection conn = ds.getConnection(); Statement stmt = conn.createStatement()) { stmt.execute("DROP TABLE IF EXISTS basic_pool_test"); stmt.execute("CREATE TABLE basic_pool_test (" + "id INTEGER NOT NULL PRIMARY KEY, " + "timestamp TIMESTAMP, " + "string VARCHAR(128), " + "string_from_number NUMERIC " + ")"); } } @Test public void testIdleTimeout() throws InterruptedException, SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(5); config.setMaximumPoolSize(10); config.setConnectionTestQuery("SELECT 1"); config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource"); config.addDataSourceProperty("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"); System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "1000"); try (HikariDataSource ds = new HikariDataSource(config)) { getUnsealedConfig(ds).setIdleTimeout(3000); System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs"); SECONDS.sleep(1); HikariPool pool = getPool(ds); assertEquals("Total connections not as expected", 5, pool.getTotalConnections()); assertEquals("Idle connections not as expected", 5, pool.getIdleConnections()); try (Connection connection = ds.getConnection()) { Assert.assertNotNull(connection); MILLISECONDS.sleep(1500); assertEquals("Second total connections not as expected", 6, pool.getTotalConnections()); assertEquals("Second idle connections not as expected", 5, pool.getIdleConnections()); } assertEquals("Idle connections not as expected", 6, pool.getIdleConnections()); MILLISECONDS.sleep(3000); assertEquals("Third total connections not as expected", 5, pool.getTotalConnections()); assertEquals("Third idle connections not as expected", 5, pool.getIdleConnections()); } } @Test public void testIdleTimeout2() throws InterruptedException, SQLException { HikariConfig config = newHikariConfig(); config.setMaximumPoolSize(50); config.setConnectionTestQuery("SELECT 1"); config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource"); config.addDataSourceProperty("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"); System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "1000"); try (HikariDataSource ds = new HikariDataSource(config)) { System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs"); SECONDS.sleep(3); HikariPool pool = getPool(ds); getUnsealedConfig(ds).setIdleTimeout(3000); assertEquals("Total connections not as expected", 50, pool.getTotalConnections()); assertEquals("Idle connections not as expected", 50, pool.getIdleConnections()); try (Connection connection = ds.getConnection()) { assertNotNull(connection); MILLISECONDS.sleep(1500); assertEquals("Second total connections not as expected", 50, pool.getTotalConnections()); assertEquals("Second idle connections not as expected", 49, pool.getIdleConnections()); } assertEquals("Idle connections not as expected", 50, pool.getIdleConnections()); SECONDS.sleep(3); assertEquals("Third total connections not as expected", 50, pool.getTotalConnections()); assertEquals("Third idle connections not as expected", 50, pool.getIdleConnections()); } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/metrics/dropwizard/CodaHaleMetricsTrackerTest.java ================================================ package com.zaxxer.hikari.metrics.dropwizard; import com.codahale.metrics.MetricRegistry; import com.zaxxer.hikari.mocks.StubPoolStats; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class CodaHaleMetricsTrackerTest { @Mock public MetricRegistry mockMetricRegistry; private CodaHaleMetricsTracker testee; @Before public void setup() { testee = new CodaHaleMetricsTracker("mypool", new StubPoolStats(0), mockMetricRegistry); } @Test public void close() { testee.close(); verify(mockMetricRegistry).remove("mypool.pool.Wait"); verify(mockMetricRegistry).remove("mypool.pool.Usage"); verify(mockMetricRegistry).remove("mypool.pool.ConnectionCreation"); verify(mockMetricRegistry).remove("mypool.pool.ConnectionTimeoutRate"); verify(mockMetricRegistry).remove("mypool.pool.TotalConnections"); verify(mockMetricRegistry).remove("mypool.pool.IdleConnections"); verify(mockMetricRegistry).remove("mypool.pool.ActiveConnections"); verify(mockMetricRegistry).remove("mypool.pool.PendingConnections"); verify(mockMetricRegistry).remove("mypool.pool.MaxConnections"); verify(mockMetricRegistry).remove("mypool.pool.MinConnections"); } } ================================================ FILE: src/test/java/com/zaxxer/hikari/metrics/dropwizard/Dropwizard5MetricsTrackerTest.java ================================================ package com.zaxxer.hikari.metrics.dropwizard; import com.zaxxer.hikari.mocks.StubPoolStats; import io.dropwizard.metrics5.MetricRegistry; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class Dropwizard5MetricsTrackerTest { @Mock public MetricRegistry mockMetricRegistry; private Dropwizard5MetricsTracker testee; @Before public void setup() { testee = new Dropwizard5MetricsTracker("mypool", new StubPoolStats(0), mockMetricRegistry); } @Test public void close() { testee.close(); verify(mockMetricRegistry).remove(MetricRegistry.name("mypool.pool.Wait")); verify(mockMetricRegistry).remove(MetricRegistry.name("mypool.pool.Usage")); verify(mockMetricRegistry).remove(MetricRegistry.name("mypool.pool.ConnectionCreation")); verify(mockMetricRegistry).remove(MetricRegistry.name("mypool.pool.ConnectionTimeoutRate")); verify(mockMetricRegistry).remove(MetricRegistry.name("mypool.pool.TotalConnections")); verify(mockMetricRegistry).remove(MetricRegistry.name("mypool.pool.IdleConnections")); verify(mockMetricRegistry).remove(MetricRegistry.name("mypool.pool.ActiveConnections")); verify(mockMetricRegistry).remove(MetricRegistry.name("mypool.pool.PendingConnections")); verify(mockMetricRegistry).remove(MetricRegistry.name("mypool.pool.MaxConnections")); verify(mockMetricRegistry).remove(MetricRegistry.name("mypool.pool.MinConnections")); } } ================================================ FILE: src/test/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerTest.java ================================================ package com.zaxxer.hikari.metrics.micrometer; import com.zaxxer.hikari.mocks.StubPoolStats; 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 StubPoolStats(1000L), mockMeterRegistry); } @Test public void close() { Assert.assertNotNull(mockMeterRegistry.find("hikaricp.connections.acquire").tag("pool", "mypool").timer()); Assert.assertNotNull(mockMeterRegistry.find("hikaricp.connections.usage").tag("pool", "mypool").timer()); Assert.assertNotNull(mockMeterRegistry.find("hikaricp.connections.creation").tag("pool", "mypool").timer()); Assert.assertNotNull(mockMeterRegistry.find("hikaricp.connections.timeout").tag("pool", "mypool").counter()); Assert.assertNotNull(mockMeterRegistry.find("hikaricp.connections").tag("pool", "mypool").gauge()); Assert.assertNotNull(mockMeterRegistry.find("hikaricp.connections.idle").tag("pool", "mypool").gauge()); Assert.assertNotNull(mockMeterRegistry.find("hikaricp.connections.active").tag("pool", "mypool").gauge()); Assert.assertNotNull(mockMeterRegistry.find("hikaricp.connections.pending").tag("pool", "mypool").gauge()); Assert.assertNotNull(mockMeterRegistry.find("hikaricp.connections.max").tag("pool", "mypool").gauge()); Assert.assertNotNull(mockMeterRegistry.find("hikaricp.connections.min").tag("pool", "mypool").gauge()); testee.close(); } } ================================================ FILE: src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics.prometheus; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertNull; import static org.hamcrest.MatcherAssert.assertThat; import java.sql.Connection; import java.util.List; import com.zaxxer.hikari.metrics.PoolStats; import io.prometheus.client.Collector; import org.junit.Before; import org.junit.Test; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.mocks.StubConnection; import io.prometheus.client.CollectorRegistry; public class HikariCPCollectorTest { private CollectorRegistry collectorRegistry; @Before public void setupCollectorRegistry() { this.collectorRegistry = new CollectorRegistry(); } @Test public void noConnection() { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(this.collectorRegistry)); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); StubConnection.slowCreate = true; try (HikariDataSource ds = new HikariDataSource(config)) { assertThat(getValue("hikaricp_active_connections", "noConnection"), is(0.0)); assertThat(getValue("hikaricp_idle_connections", "noConnection"), is(0.0)); assertThat(getValue("hikaricp_pending_threads", "noConnection"), is(0.0)); assertThat(getValue("hikaricp_connections", "noConnection"), is(0.0)); assertThat(getValue("hikaricp_max_connections", "noConnection"), is(10.0)); assertThat(getValue("hikaricp_min_connections", "noConnection"), is(0.0)); } finally { StubConnection.slowCreate = false; } } @Test public void noConnectionWithoutPoolName() { HikariConfig config = new HikariConfig(); config.setMinimumIdle(0); config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(this.collectorRegistry)); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); StubConnection.slowCreate = true; try (HikariDataSource ds = new HikariDataSource(config)) { 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)); assertThat(getValue("hikaricp_max_connections", poolName), is(10.0)); assertThat(getValue("hikaricp_min_connections", poolName), is(0.0)); } finally { StubConnection.slowCreate = false; } } @Test public void connection1() throws Exception { HikariConfig config = newHikariConfig(); config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(this.collectorRegistry)); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); config.setMaximumPoolSize(1); StubConnection.slowCreate = true; try (HikariDataSource ds = new HikariDataSource(config); Connection connection1 = ds.getConnection()) { quietlySleep(1000); assertThat(getValue("hikaricp_active_connections", "connection1"), is(1.0)); assertThat(getValue("hikaricp_idle_connections", "connection1"), is(0.0)); assertThat(getValue("hikaricp_pending_threads", "connection1"), is(0.0)); assertThat(getValue("hikaricp_connections", "connection1"), is(1.0)); assertThat(getValue("hikaricp_max_connections", "connection1"), is(1.0)); assertThat(getValue("hikaricp_min_connections", "connection1"), is(1.0)); } finally { StubConnection.slowCreate = false; } } @Test public void connectionClosed() throws Exception { HikariConfig config = newHikariConfig(); config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(this.collectorRegistry)); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); config.setMaximumPoolSize(1); StubConnection.slowCreate = true; try (HikariDataSource ds = new HikariDataSource(config)) { 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)); assertThat(getValue("hikaricp_connections", "connectionClosed"), is(1.0)); assertThat(getValue("hikaricp_max_connections", "connectionClosed"), is(1.0)); assertThat(getValue("hikaricp_min_connections", "connectionClosed"), is(1.0)); } finally { StubConnection.slowCreate = false; } } @Test public void poolStatsRemovedAfterShutDown() throws Exception { HikariConfig config = new HikariConfig(); config.setPoolName("shutDownPool"); config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(this.collectorRegistry)); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); config.setMaximumPoolSize(1); StubConnection.slowCreate = true; try (HikariDataSource ds = new HikariDataSource(config)) { try (Connection connection1 = ds.getConnection()) { // close immediately } assertThat(getValue("hikaricp_active_connections", "shutDownPool"), is(0.0)); assertThat(getValue("hikaricp_idle_connections", "shutDownPool"), is(1.0)); assertThat(getValue("hikaricp_pending_threads", "shutDownPool"), is(0.0)); assertThat(getValue("hikaricp_connections", "shutDownPool"), is(1.0)); assertThat(getValue("hikaricp_max_connections", "shutDownPool"), is(1.0)); assertThat(getValue("hikaricp_min_connections", "shutDownPool"), is(1.0)); } finally { StubConnection.slowCreate = false; } assertNull(getValue("hikaricp_active_connections", "shutDownPool")); assertNull(getValue("hikaricp_idle_connections", "shutDownPool")); assertNull(getValue("hikaricp_pending_threads", "shutDownPool")); assertNull(getValue("hikaricp_connections", "shutDownPool")); assertNull(getValue("hikaricp_max_connections", "shutDownPool")); assertNull(getValue("hikaricp_min_connections", "shutDownPool")); } @Test public void testHikariCPCollectorGaugesMetricsInitialization() { HikariCPCollector hikariCPCollector = new HikariCPCollector(); hikariCPCollector.add("collectorTestPool", poolStatsWithPredefinedValues()); List metrics = hikariCPCollector.collect(); hikariCPCollector.register(collectorRegistry); assertThat(metrics.size(), is(6)); assertThat(metrics.stream().filter(metricFamilySamples -> metricFamilySamples.type == Collector.Type.GAUGE).count(), is(6L)); assertThat(getValue("hikaricp_active_connections", "collectorTestPool"), is(58.0)); assertThat(getValue("hikaricp_idle_connections", "collectorTestPool"), is(42.0)); assertThat(getValue("hikaricp_pending_threads", "collectorTestPool"), is(1.0)); assertThat(getValue("hikaricp_connections", "collectorTestPool"), is(100.0)); assertThat(getValue("hikaricp_max_connections", "collectorTestPool"), is(100.0)); assertThat(getValue("hikaricp_min_connections", "collectorTestPool"), is(3.0)); } private Double getValue(String name, String poolName) { String[] labelNames = {"pool"}; String[] labelValues = {poolName}; return this.collectorRegistry.getSampleValue(name, labelNames, labelValues); } private PoolStats poolStatsWithPredefinedValues() { return new PoolStats(0) { @Override protected void update() { totalConnections = 100; idleConnections = 42; activeConnections = 58; pendingThreads = 1; maxConnections = 100; minConnections = 3; } }; } } ================================================ FILE: src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusHistogramMetricsTrackerFactoryTest.java ================================================ package com.zaxxer.hikari.metrics.prometheus; import com.zaxxer.hikari.metrics.PoolStats; import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; import org.junit.After; import org.junit.Test; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class PrometheusHistogramMetricsTrackerFactoryTest { @Test public void registersToProvidedCollectorRegistry() { CollectorRegistry collectorRegistry = new CollectorRegistry(); PrometheusHistogramMetricsTrackerFactory factory = new PrometheusHistogramMetricsTrackerFactory(collectorRegistry); factory.create("testpool-1", poolStats()); assertHikariMetricsAreNotPresent(CollectorRegistry.defaultRegistry); assertHikariMetricsArePresent(collectorRegistry); } @Test public void registersToDefaultCollectorRegistry() { PrometheusHistogramMetricsTrackerFactory factory = new PrometheusHistogramMetricsTrackerFactory(); factory.create("testpool-2", poolStats()); assertHikariMetricsArePresent(CollectorRegistry.defaultRegistry); } @After public void clearCollectorRegistry(){ CollectorRegistry.defaultRegistry.clear(); } private void assertHikariMetricsArePresent(CollectorRegistry collectorRegistry) { List registeredMetrics = toMetricNames(collectorRegistry.metricFamilySamples()); assertTrue(registeredMetrics.contains("hikaricp_active_connections")); assertTrue(registeredMetrics.contains("hikaricp_idle_connections")); assertTrue(registeredMetrics.contains("hikaricp_pending_threads")); assertTrue(registeredMetrics.contains("hikaricp_connections")); assertTrue(registeredMetrics.contains("hikaricp_max_connections")); assertTrue(registeredMetrics.contains("hikaricp_min_connections")); } private void assertHikariMetricsAreNotPresent(CollectorRegistry collectorRegistry) { List registeredMetrics = toMetricNames(collectorRegistry.metricFamilySamples()); assertFalse(registeredMetrics.contains("hikaricp_active_connections")); assertFalse(registeredMetrics.contains("hikaricp_idle_connections")); assertFalse(registeredMetrics.contains("hikaricp_pending_threads")); assertFalse(registeredMetrics.contains("hikaricp_connections")); assertFalse(registeredMetrics.contains("hikaricp_max_connections")); assertFalse(registeredMetrics.contains("hikaricp_min_connections")); } private List toMetricNames(Enumeration enumeration) { List list = new ArrayList<>(); while (enumeration.hasMoreElements()) { list.add(enumeration.nextElement().name); } return list; } private PoolStats poolStats() { return new PoolStats(0) { @Override protected void update() { // do nothing } }; } } ================================================ FILE: src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusHistogramMetricsTrackerTest.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics.prometheus; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import io.prometheus.client.CollectorRegistry; import org.junit.Before; import org.junit.Test; import java.sql.Connection; import java.sql.SQLTransientConnectionException; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertNotNull; import static org.hamcrest.MatcherAssert.assertThat; public class PrometheusHistogramMetricsTrackerTest { private CollectorRegistry defaultCollectorRegistry; private CollectorRegistry customCollectorRegistry; private static final String POOL_LABEL_NAME = "pool"; private static final String[] LABEL_NAMES = {POOL_LABEL_NAME}; @Before public void setupCollectorRegistry() { this.defaultCollectorRegistry = new CollectorRegistry(); this.customCollectorRegistry = new CollectorRegistry(); } @Test public void recordConnectionTimeout() throws Exception { HikariConfig config = newHikariConfig(); config.setMetricsTrackerFactory(new PrometheusHistogramMetricsTrackerFactory(defaultCollectorRegistry)); config.setJdbcUrl("jdbc:h2:mem:"); config.setMaximumPoolSize(2); config.setConnectionTimeout(250); String[] labelValues = {config.getPoolName()}; try (HikariDataSource hikariDataSource = new HikariDataSource(config)) { try (Connection connection1 = hikariDataSource.getConnection(); Connection connection2 = hikariDataSource.getConnection()) { try (Connection connection3 = hikariDataSource.getConnection()) { } catch (SQLTransientConnectionException ignored) { } } Double total = defaultCollectorRegistry.getSampleValue( "hikaricp_connection_timeout_total", LABEL_NAMES, labelValues ); assertThat(total, is(1.0)); } } @Test public void connectionAcquisitionMetrics() { checkSummaryMetricFamily("hikaricp_connection_acquired_nanos"); } @Test public void connectionUsageMetrics() { checkSummaryMetricFamily("hikaricp_connection_usage_millis"); } @Test public void connectionCreationMetrics() { checkSummaryMetricFamily("hikaricp_connection_creation_millis"); } @Test public void testMultiplePoolNameWithOneCollectorRegistry() { HikariConfig configFirstPool = newHikariConfig(); configFirstPool.setMetricsTrackerFactory(new PrometheusHistogramMetricsTrackerFactory(defaultCollectorRegistry)); configFirstPool.setPoolName("first"); configFirstPool.setJdbcUrl("jdbc:h2:mem:"); configFirstPool.setMaximumPoolSize(2); configFirstPool.setConnectionTimeout(250); HikariConfig configSecondPool = newHikariConfig(); configSecondPool.setMetricsTrackerFactory(new PrometheusHistogramMetricsTrackerFactory(defaultCollectorRegistry)); configSecondPool.setPoolName("second"); configSecondPool.setJdbcUrl("jdbc:h2:mem:"); configSecondPool.setMaximumPoolSize(4); configSecondPool.setConnectionTimeout(250); String[] labelValuesFirstPool = {configFirstPool.getPoolName()}; String[] labelValuesSecondPool = {configSecondPool.getPoolName()}; try (HikariDataSource ignoredFirstPool = new HikariDataSource(configFirstPool)) { assertThat(defaultCollectorRegistry.getSampleValue( "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesFirstPool), is(0.0)); try (HikariDataSource ignoredSecondPool = new HikariDataSource(configSecondPool)) { assertThat(defaultCollectorRegistry.getSampleValue( "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesSecondPool), is(0.0)); } } } @Test public void testMultiplePoolNameWithDifferentCollectorRegistries() { HikariConfig configFirstPool = newHikariConfig(); configFirstPool.setMetricsTrackerFactory(new PrometheusHistogramMetricsTrackerFactory(defaultCollectorRegistry)); configFirstPool.setPoolName("first"); configFirstPool.setJdbcUrl("jdbc:h2:mem:"); configFirstPool.setMaximumPoolSize(2); configFirstPool.setConnectionTimeout(250); HikariConfig configSecondPool = newHikariConfig(); configSecondPool.setMetricsTrackerFactory(new PrometheusHistogramMetricsTrackerFactory(customCollectorRegistry)); configSecondPool.setPoolName("second"); configSecondPool.setJdbcUrl("jdbc:h2:mem:"); configSecondPool.setMaximumPoolSize(4); configSecondPool.setConnectionTimeout(250); String[] labelValuesFirstPool = {configFirstPool.getPoolName()}; String[] labelValuesSecondPool = {configSecondPool.getPoolName()}; try (HikariDataSource ignoredFirstPool = new HikariDataSource(configFirstPool)) { assertThat(defaultCollectorRegistry.getSampleValue( "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesFirstPool), is(0.0)); try (HikariDataSource ignoredSecondPool = new HikariDataSource(configSecondPool)) { assertThat(customCollectorRegistry.getSampleValue( "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesSecondPool), is(0.0)); } } } private void checkSummaryMetricFamily(String metricName) { HikariConfig config = newHikariConfig(); config.setMetricsTrackerFactory(new PrometheusHistogramMetricsTrackerFactory(defaultCollectorRegistry)); config.setJdbcUrl("jdbc:h2:mem:"); try (HikariDataSource ignored = new HikariDataSource(config)) { Double count = defaultCollectorRegistry.getSampleValue( metricName + "_count", LABEL_NAMES, new String[]{config.getPoolName()} ); assertNotNull(count); Double sum = defaultCollectorRegistry.getSampleValue( metricName + "_sum", LABEL_NAMES, new String[]{config.getPoolName()} ); assertNotNull(sum); } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerFactoryTest.java ================================================ package com.zaxxer.hikari.metrics.prometheus; import com.zaxxer.hikari.mocks.StubPoolStats; import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; import org.junit.After; import org.junit.Test; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class PrometheusMetricsTrackerFactoryTest { @After public void clearCollectorRegistry() { CollectorRegistry.defaultRegistry.clear(); } @Test public void registersToProvidedCollectorRegistry() { CollectorRegistry collectorRegistry = new CollectorRegistry(); PrometheusMetricsTrackerFactory factory = new PrometheusMetricsTrackerFactory(collectorRegistry); factory.create("testpool-1", new StubPoolStats(0)); assertHikariMetricsAreNotPresent(CollectorRegistry.defaultRegistry); assertHikariMetricsArePresent(collectorRegistry); } @Test public void registersToDefaultCollectorRegistry() { PrometheusMetricsTrackerFactory factory = new PrometheusMetricsTrackerFactory(); factory.create("testpool-2", new StubPoolStats(0)); assertHikariMetricsArePresent(CollectorRegistry.defaultRegistry); } private void assertHikariMetricsArePresent(CollectorRegistry collectorRegistry) { List registeredMetrics = toMetricNames(collectorRegistry.metricFamilySamples()); assertTrue(registeredMetrics.contains("hikaricp_active_connections")); assertTrue(registeredMetrics.contains("hikaricp_idle_connections")); assertTrue(registeredMetrics.contains("hikaricp_pending_threads")); assertTrue(registeredMetrics.contains("hikaricp_connections")); assertTrue(registeredMetrics.contains("hikaricp_max_connections")); assertTrue(registeredMetrics.contains("hikaricp_min_connections")); } private void assertHikariMetricsAreNotPresent(CollectorRegistry collectorRegistry) { List registeredMetrics = toMetricNames(collectorRegistry.metricFamilySamples()); assertFalse(registeredMetrics.contains("hikaricp_active_connections")); assertFalse(registeredMetrics.contains("hikaricp_idle_connections")); assertFalse(registeredMetrics.contains("hikaricp_pending_threads")); assertFalse(registeredMetrics.contains("hikaricp_connections")); assertFalse(registeredMetrics.contains("hikaricp_max_connections")); assertFalse(registeredMetrics.contains("hikaricp_min_connections")); } private List toMetricNames(Enumeration enumeration) { List list = new ArrayList<>(); while (enumeration.hasMoreElements()) { list.add(enumeration.nextElement().name); } return list; } } ================================================ FILE: src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.metrics.prometheus; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.metrics.IMetricsTracker; import com.zaxxer.hikari.mocks.StubPoolStats; import io.prometheus.client.CollectorRegistry; import org.junit.Before; import org.junit.Test; import java.sql.Connection; import java.sql.SQLTransientConnectionException; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.hamcrest.MatcherAssert.assertThat; public class PrometheusMetricsTrackerTest { private CollectorRegistry defaultCollectorRegistry; private CollectorRegistry customCollectorRegistry; private static final String POOL_LABEL_NAME = "pool"; private static final String[] LABEL_NAMES = {POOL_LABEL_NAME}; private static final String QUANTILE_LABEL_NAME = "quantile"; private static final String[] QUANTILE_LABEL_VALUES = new String[]{"0.5", "0.95", "0.99"}; @Before public void setupCollectorRegistry() { this.defaultCollectorRegistry = new CollectorRegistry(); this.customCollectorRegistry = new CollectorRegistry(); } @Test public void recordConnectionTimeout() throws Exception { HikariConfig config = newHikariConfig(); config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(defaultCollectorRegistry)); config.setJdbcUrl("jdbc:h2:mem:"); config.setMaximumPoolSize(2); config.setConnectionTimeout(250); String[] labelValues = {config.getPoolName()}; try (HikariDataSource hikariDataSource = new HikariDataSource(config)) { try (Connection connection1 = hikariDataSource.getConnection(); Connection connection2 = hikariDataSource.getConnection()) { try (Connection connection3 = hikariDataSource.getConnection()) { } catch (SQLTransientConnectionException ignored) { } } Double total = defaultCollectorRegistry.getSampleValue( "hikaricp_connection_timeout_total", LABEL_NAMES, labelValues ); assertThat(total, is(1.0)); } } @Test public void connectionAcquisitionMetrics() { checkSummaryMetricFamily("hikaricp_connection_acquired_nanos"); } @Test public void connectionUsageMetrics() { checkSummaryMetricFamily("hikaricp_connection_usage_millis"); } @Test public void connectionCreationMetrics() { checkSummaryMetricFamily("hikaricp_connection_creation_millis"); } @Test public void testMultiplePoolNameWithOneCollectorRegistry() { HikariConfig configFirstPool = newHikariConfig(); configFirstPool.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(defaultCollectorRegistry)); configFirstPool.setPoolName("first"); configFirstPool.setJdbcUrl("jdbc:h2:mem:"); configFirstPool.setMaximumPoolSize(2); configFirstPool.setConnectionTimeout(250); HikariConfig configSecondPool = newHikariConfig(); configSecondPool.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(defaultCollectorRegistry)); configSecondPool.setPoolName("second"); configSecondPool.setJdbcUrl("jdbc:h2:mem:"); configSecondPool.setMaximumPoolSize(4); configSecondPool.setConnectionTimeout(250); String[] labelValuesFirstPool = {configFirstPool.getPoolName()}; String[] labelValuesSecondPool = {configSecondPool.getPoolName()}; try (HikariDataSource ignoredFirstPool = new HikariDataSource(configFirstPool)) { assertThat(defaultCollectorRegistry.getSampleValue( "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesFirstPool), is(0.0)); try (HikariDataSource ignoredSecondPool = new HikariDataSource(configSecondPool)) { assertThat(defaultCollectorRegistry.getSampleValue( "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesSecondPool), is(0.0)); } } } @Test public void testMultiplePoolNameWithDifferentCollectorRegistries() { HikariConfig configFirstPool = newHikariConfig(); configFirstPool.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(defaultCollectorRegistry)); configFirstPool.setPoolName("first"); configFirstPool.setJdbcUrl("jdbc:h2:mem:"); configFirstPool.setMaximumPoolSize(2); configFirstPool.setConnectionTimeout(250); HikariConfig configSecondPool = newHikariConfig(); configSecondPool.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(customCollectorRegistry)); configSecondPool.setPoolName("second"); configSecondPool.setJdbcUrl("jdbc:h2:mem:"); configSecondPool.setMaximumPoolSize(4); configSecondPool.setConnectionTimeout(250); String[] labelValuesFirstPool = {configFirstPool.getPoolName()}; String[] labelValuesSecondPool = {configSecondPool.getPoolName()}; try (HikariDataSource ignoredFirstPool = new HikariDataSource(configFirstPool)) { assertThat(defaultCollectorRegistry.getSampleValue( "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesFirstPool), is(0.0)); try (HikariDataSource ignoredSecondPool = new HikariDataSource(configSecondPool)) { assertThat(customCollectorRegistry.getSampleValue( "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesSecondPool), is(0.0)); } } } @Test public void testMetricsRemovedAfterShutDown() { HikariConfig configFirstPool = newHikariConfig(); configFirstPool.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(defaultCollectorRegistry)); configFirstPool.setPoolName("first"); configFirstPool.setJdbcUrl("jdbc:h2:mem:"); configFirstPool.setMaximumPoolSize(2); configFirstPool.setConnectionTimeout(250); HikariConfig configSecondPool = newHikariConfig(); configSecondPool.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(customCollectorRegistry)); configSecondPool.setPoolName("second"); configSecondPool.setJdbcUrl("jdbc:h2:mem:"); configSecondPool.setMaximumPoolSize(4); configSecondPool.setConnectionTimeout(250); String[] labelValuesFirstPool = {configFirstPool.getPoolName()}; String[] labelValuesSecondPool = {configSecondPool.getPoolName()}; try (HikariDataSource ignoredFirstPool = new HikariDataSource(configFirstPool)) { assertThat(defaultCollectorRegistry.getSampleValue( "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesFirstPool), is(0.0)); try (HikariDataSource ignoredSecondPool = new HikariDataSource(configSecondPool)) { assertThat(customCollectorRegistry.getSampleValue( "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesSecondPool), is(0.0)); } assertNull(defaultCollectorRegistry.getSampleValue( "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesSecondPool)); assertThat(defaultCollectorRegistry.getSampleValue( "hikaricp_connection_timeout_total", LABEL_NAMES, labelValuesFirstPool), is(0.0)); } } @Test public void testCloseMethod() { String[] labelValues = {"testPool"}; PrometheusMetricsTrackerFactory prometheusFactory = new PrometheusMetricsTrackerFactory(defaultCollectorRegistry); IMetricsTracker prometheusTracker = prometheusFactory.create("testPool", new StubPoolStats(0)); prometheusTracker.recordConnectionTimeout(); prometheusTracker.recordConnectionAcquiredNanos(42L); prometheusTracker.recordConnectionUsageMillis(111L); prometheusTracker.recordConnectionCreatedMillis(101L); assertThat(defaultCollectorRegistry.getSampleValue( "hikaricp_connection_timeout_total", LABEL_NAMES, labelValues), is(1.0)); assertThat(defaultCollectorRegistry.getSampleValue( "hikaricp_connection_acquired_nanos_sum", LABEL_NAMES, labelValues), is(42.0)); assertThat(defaultCollectorRegistry.getSampleValue( "hikaricp_connection_usage_millis_sum", LABEL_NAMES, labelValues), is(111.0)); assertThat(defaultCollectorRegistry.getSampleValue( "hikaricp_connection_creation_millis_sum", LABEL_NAMES, labelValues), is(101.0)); assertThat(defaultCollectorRegistry.getSampleValue( "hikaricp_active_connections", LABEL_NAMES, labelValues), is(0.0)); assertThat(defaultCollectorRegistry.getSampleValue( "hikaricp_idle_connections", LABEL_NAMES, labelValues), is(0.0)); assertThat(defaultCollectorRegistry.getSampleValue( "hikaricp_pending_threads", LABEL_NAMES, labelValues), is(0.0)); assertThat(defaultCollectorRegistry.getSampleValue( "hikaricp_connections", LABEL_NAMES, labelValues), is(0.0)); assertThat(defaultCollectorRegistry.getSampleValue( "hikaricp_max_connections", LABEL_NAMES, labelValues), is(0.0)); assertThat(defaultCollectorRegistry.getSampleValue( "hikaricp_min_connections", LABEL_NAMES, labelValues), is(0.0)); prometheusTracker.close(); assertNull(defaultCollectorRegistry.getSampleValue( "hikaricp_connection_timeout_total", LABEL_NAMES, labelValues)); assertNull(defaultCollectorRegistry.getSampleValue( "hikaricp_connection_acquired_nanos_sum", LABEL_NAMES, labelValues)); assertNull(defaultCollectorRegistry.getSampleValue( "hikaricp_connection_usage_millis_sum", LABEL_NAMES, labelValues)); assertNull(defaultCollectorRegistry.getSampleValue( "hikaricp_connection_creation_millis_sum", LABEL_NAMES, labelValues)); assertNull(defaultCollectorRegistry.getSampleValue( "hikaricp_active_connections", LABEL_NAMES, labelValues)); assertNull(defaultCollectorRegistry.getSampleValue( "hikaricp_idle_connections", LABEL_NAMES, labelValues)); assertNull(defaultCollectorRegistry.getSampleValue( "hikaricp_pending_threads", LABEL_NAMES, labelValues)); assertNull(defaultCollectorRegistry.getSampleValue( "hikaricp_connections", LABEL_NAMES, labelValues)); assertNull(defaultCollectorRegistry.getSampleValue( "hikaricp_connections", LABEL_NAMES, labelValues)); assertNull(defaultCollectorRegistry.getSampleValue( "hikaricp_max_connections", LABEL_NAMES, labelValues)); assertNull(defaultCollectorRegistry.getSampleValue( "hikaricp_min_connections", LABEL_NAMES, labelValues)); } private void checkSummaryMetricFamily(String metricName) { HikariConfig config = newHikariConfig(); config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory(defaultCollectorRegistry)); config.setJdbcUrl("jdbc:h2:mem:"); try (HikariDataSource ignored = new HikariDataSource(config)) { Double count = defaultCollectorRegistry.getSampleValue( metricName + "_count", LABEL_NAMES, new String[]{config.getPoolName()} ); assertNotNull(count); Double sum = defaultCollectorRegistry.getSampleValue( metricName + "_sum", LABEL_NAMES, new String[]{config.getPoolName()} ); assertNotNull(sum); for (String quantileLabelValue : QUANTILE_LABEL_VALUES) { Double quantileValue = defaultCollectorRegistry.getSampleValue( metricName, new String[]{POOL_LABEL_NAME, QUANTILE_LABEL_NAME}, new String[]{config.getPoolName(), quantileLabelValue} ); assertNotNull("q = " + quantileLabelValue, quantileValue); } } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/mocks/MockDataSource.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.mocks; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.PrintWriter; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.Statement; import java.util.logging.Logger; import javax.sql.DataSource; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; /** * * @author Brett Wooldridge */ public class MockDataSource implements DataSource { @Override public Connection getConnection() throws SQLException { return createMockConnection(); } @Override public Connection getConnection(String username, String password) throws SQLException { return getConnection(); } @Override public PrintWriter getLogWriter() throws SQLException { return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { } @Override public void setLoginTimeout(int seconds) throws SQLException { } @Override public int getLoginTimeout() throws SQLException { return 0; } public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } @Override public T unwrap(Class iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class iface) throws SQLException { return false; } public static Connection createMockConnection() throws SQLException { // Setup mock connection final Connection mockConnection = mock(Connection.class); // Autocommit is always true by default when(mockConnection.getAutoCommit()).thenReturn(true); // Handle Connection.createStatement() Statement statement = mock(Statement.class); when(mockConnection.createStatement()).thenReturn(statement); when(mockConnection.createStatement(anyInt(), anyInt())).thenReturn(statement); when(mockConnection.createStatement(anyInt(), anyInt(), anyInt())).thenReturn(statement); when(mockConnection.isValid(anyInt())).thenReturn(true); // Handle Connection.prepareStatement() PreparedStatement mockPreparedStatement = mock(PreparedStatement.class); when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); when(mockConnection.prepareStatement(anyString(), anyInt())).thenReturn(mockPreparedStatement); when(mockConnection.prepareStatement(anyString(), any(int[].class))).thenReturn(mockPreparedStatement); when(mockConnection.prepareStatement(anyString(), any(String[].class))).thenReturn(mockPreparedStatement); when(mockConnection.prepareStatement(anyString(), anyInt(), anyInt())).thenReturn(mockPreparedStatement); when(mockConnection.prepareStatement(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(mockPreparedStatement); doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { return null; } }).doNothing().when(mockPreparedStatement).setInt(anyInt(), anyInt()); ResultSet mockResultSet = mock(ResultSet.class); when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); when(mockResultSet.getString(anyInt())).thenReturn("aString"); when(mockResultSet.next()).thenReturn(true); // Handle Connection.prepareCall() CallableStatement mockCallableStatement = mock(CallableStatement.class); when(mockConnection.prepareCall(anyString())).thenReturn(mockCallableStatement); when(mockConnection.prepareCall(anyString(), anyInt(), anyInt())).thenReturn(mockCallableStatement); when(mockConnection.prepareCall(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(mockCallableStatement); // Handle Connection.close() // doAnswer(new Answer() { // public Void answer(InvocationOnMock invocation) throws Throwable { // return null; // } // }).doThrow(new SQLException("Connection is already closed")).when(mockConnection).close(); // Handle Connection.commit() // doAnswer(new Answer() { // public Void answer(InvocationOnMock invocation) throws Throwable { // return null; // } // }).doThrow(new SQLException("Transaction already committed")).when(mockConnection).commit(); // Handle Connection.rollback() // doAnswer(new Answer() { // public Void answer(InvocationOnMock invocation) throws Throwable { // return null; // } // }).doThrow(new SQLException("Transaction already rolledback")).when(mockConnection).rollback(); return mockConnection; } } ================================================ FILE: src/test/java/com/zaxxer/hikari/mocks/StubBaseConnection.java ================================================ /* * 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.mocks; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; public abstract class StubBaseConnection implements Connection { public volatile boolean throwException; /** {@inheritDoc} */ @Override public Statement createStatement() throws SQLException { if (throwException) { throw new SQLException(); } return new StubStatement(this); } /** {@inheritDoc} */ @Override public PreparedStatement prepareStatement(String sql) throws SQLException { if (throwException) { throw new SQLException(); } return new StubPreparedStatement(this); } } ================================================ FILE: src/test/java/com/zaxxer/hikari/mocks/StubConnection.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.mocks; import java.sql.Array; import java.sql.Blob; import java.sql.CallableStatement; import java.sql.Clob; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.NClob; import java.sql.PreparedStatement; import java.sql.SQLClientInfoException; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.SQLXML; import java.sql.Savepoint; import java.sql.Statement; import java.sql.Struct; import java.util.Map; import java.util.Properties; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import com.zaxxer.hikari.util.UtilityElf; /** * * @author Brett Wooldridge * @author Yanming Zhou */ public class StubConnection extends StubBaseConnection { public static final AtomicInteger count = new AtomicInteger(); public static volatile boolean slowCreate; public static volatile boolean oldDriver; public static volatile Callable networkTimeoutSetter; private volatile boolean isClosed = false; private static long foo; private boolean autoCommit; private int isolation = Connection.TRANSACTION_READ_COMMITTED; private String catalog; private String schema; private long waitTimeout; public boolean beginRequestCalled = false; public boolean endRequestCalled = false; private static ScheduledExecutorService connectionWaitTimeout = new ScheduledThreadPoolExecutor(1); private ScheduledFuture waitTimeoutTask; static { foo = System.currentTimeMillis(); } public StubConnection() { count.incrementAndGet(); if (slowCreate) { UtilityElf.quietlySleep(1000); } } public StubConnection(long waitTimeout) { this.waitTimeout = waitTimeout; count.incrementAndGet(); if (slowCreate) { UtilityElf.quietlySleep(1000); } try { refreshConnectionWaitTimeout(); } catch (Exception e){ //ignore } } /** {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public T unwrap(Class iface) throws SQLException { if (throwException) { throw new SQLException(); } if (iface.isInstance(this)) { return (T) this; } throw new SQLException("Wrapped connection is not an instance of " + iface); } /** {@inheritDoc} */ @Override public boolean isWrapperFor(Class iface) throws SQLException { if (throwException) { throw new SQLException(); } return false; } /** {@inheritDoc} */ @Override public CallableStatement prepareCall(String sql) throws SQLException { if (throwException) { throw new SQLException(); } return null; } /** {@inheritDoc} */ @Override public String nativeSQL(String sql) throws SQLException { return null; } /** {@inheritDoc} */ @Override public void setAutoCommit(boolean autoCommit) throws SQLException { if (throwException) { throw new SQLException(); } this.autoCommit = autoCommit; } /** {@inheritDoc} */ @Override public boolean getAutoCommit() throws SQLException { if (throwException) { throw new SQLException(); } return autoCommit; } /** {@inheritDoc} */ @Override public void commit() throws SQLException { refreshConnectionWaitTimeout(); } private void refreshConnectionWaitTimeout() throws SQLException { if (this.isClosed) { throw new SQLException("connection has been closed"); } if (waitTimeoutTask != null) { waitTimeoutTask.cancel(true); } if (waitTimeout > 0) { waitTimeoutTask = connectionWaitTimeout.schedule(() -> { this.isClosed = true;}, waitTimeout, TimeUnit.MILLISECONDS); } } /** {@inheritDoc} */ @Override public void rollback() throws SQLException { } /** {@inheritDoc} */ @Override public void close() throws SQLException { } /** {@inheritDoc} */ @Override public boolean isClosed() throws SQLException { if (throwException) { throw new SQLException(); } return isClosed; } /** {@inheritDoc} */ @Override public DatabaseMetaData getMetaData() throws SQLException { return null; } /** {@inheritDoc} */ @Override public void setReadOnly(boolean readOnly) throws SQLException { if (throwException) { throw new SQLException(); } } /** {@inheritDoc} */ @Override public boolean isReadOnly() throws SQLException { if (throwException) { throw new SQLException(); } return false; } /** {@inheritDoc} */ @Override public void setCatalog(String catalog) throws SQLException { if (throwException) { throw new SQLException(); } this.catalog = catalog; } /** {@inheritDoc} */ @Override public String getCatalog() throws SQLException { if (throwException) { throw new SQLException(); } return catalog; } /** {@inheritDoc} */ @Override public void setTransactionIsolation(int level) throws SQLException { if (throwException) { throw new SQLException(); } this.isolation = level; } /** {@inheritDoc} */ @Override public int getTransactionIsolation() throws SQLException { if (throwException) { throw new SQLException(); } return isolation; } /** {@inheritDoc} */ @Override public SQLWarning getWarnings() throws SQLException { return null; } /** {@inheritDoc} */ @Override public void clearWarnings() throws SQLException { if (throwException) { throw new SQLException(); } } /** {@inheritDoc} */ @Override public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { if (throwException) { throw new SQLException(); } return null; } /** {@inheritDoc} */ @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { if (throwException) { throw new SQLException(); } return new StubPreparedStatement(this); } /** {@inheritDoc} */ @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { if (throwException) { throw new SQLException(); } return null; } /** {@inheritDoc} */ @Override public Map> getTypeMap() throws SQLException { return null; } /** {@inheritDoc} */ @Override public void setTypeMap(Map> map) throws SQLException { } /** {@inheritDoc} */ @Override public void setHoldability(int holdability) throws SQLException { } /** {@inheritDoc} */ @Override public int getHoldability() throws SQLException { return (int) foo; } /** {@inheritDoc} */ @Override public Savepoint setSavepoint() throws SQLException { return null; } /** {@inheritDoc} */ @Override public Savepoint setSavepoint(String name) throws SQLException { return null; } /** {@inheritDoc} */ @Override public void rollback(Savepoint savepoint) throws SQLException { } /** {@inheritDoc} */ @Override public void releaseSavepoint(Savepoint savepoint) throws SQLException { } /** {@inheritDoc} */ @Override public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { if (throwException) { throw new SQLException(); } return null; } /** {@inheritDoc} */ @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { if (throwException) { throw new SQLException(); } return new StubPreparedStatement(this); } /** {@inheritDoc} */ @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { if (throwException) { throw new SQLException(); } return null; } /** {@inheritDoc} */ @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { if (throwException) { throw new SQLException(); } return new StubPreparedStatement(this); } /** {@inheritDoc} */ @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { if (throwException) { throw new SQLException(); } return new StubPreparedStatement(this); } /** {@inheritDoc} */ @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { if (throwException) { throw new SQLException(); } return new StubPreparedStatement(this); } /** {@inheritDoc} */ @Override public Clob createClob() throws SQLException { return null; } /** {@inheritDoc} */ @Override public Blob createBlob() throws SQLException { return null; } /** {@inheritDoc} */ @Override public NClob createNClob() throws SQLException { return null; } /** {@inheritDoc} */ @Override public SQLXML createSQLXML() throws SQLException { return null; } /** {@inheritDoc} */ @Override public boolean isValid(int timeout) throws SQLException { if (throwException) { throw new SQLException(); } refreshConnectionWaitTimeout(); return !isClosed; } /** {@inheritDoc} */ @Override public void setClientInfo(String name, String value) throws SQLClientInfoException { } /** {@inheritDoc} */ @Override public void setClientInfo(Properties properties) throws SQLClientInfoException { } /** {@inheritDoc} */ @Override public String getClientInfo(String name) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Properties getClientInfo() throws SQLException { return null; } /** {@inheritDoc} */ @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Struct createStruct(String typeName, Object[] attributes) throws SQLException { return null; } /** {@inheritDoc} */ public void setSchema(String schema) throws SQLException { if (throwException) { throw new SQLException(); } this.schema = schema; } /** {@inheritDoc} */ public String getSchema() throws SQLException { if (throwException) { throw new SQLException(); } return schema; } /** {@inheritDoc} */ public void abort(Executor executor) throws SQLException { throw new SQLException("Intentional exception during abort"); } /** {@inheritDoc} */ public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { if (throwException) { throw new SQLException(); } if (networkTimeoutSetter != null) { try { networkTimeoutSetter.call(); } catch (SQLException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } } /** {@inheritDoc} */ public int getNetworkTimeout() throws SQLException { if (throwException) { throw new SQLException(); } if (oldDriver) { throw new AbstractMethodError(); } return 0; } @Override public void beginRequest() { beginRequestCalled = true; } @Override public void endRequest() { endRequestCalled = true; } } ================================================ FILE: src/test/java/com/zaxxer/hikari/mocks/StubDataSource.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.mocks; import com.zaxxer.hikari.util.UtilityElf; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger; import javax.sql.DataSource; /** * * @author Brett Wooldridge */ public class StubDataSource implements DataSource { private String user; private String password; private PrintWriter logWriter; private SQLException throwException; private long connectionAcquisitionTime = 0; private int loginTimeout; private int waitTimeout = 30000; public String getUser() { return user; } public void setUser(String user) { this.user = user; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getWaitTimeout() { return waitTimeout; } public void setWaitTimeout(int waitTimeout) { this.waitTimeout = waitTimeout; } public void setURL(String url) { // we don't care } /** {@inheritDoc} */ @Override public PrintWriter getLogWriter() throws SQLException { return logWriter; } /** {@inheritDoc} */ @Override public void setLogWriter(PrintWriter out) throws SQLException { this.logWriter = out; } /** {@inheritDoc} */ @Override public void setLoginTimeout(int seconds) throws SQLException { this.loginTimeout = seconds; } /** {@inheritDoc} */ @Override public int getLoginTimeout() throws SQLException { return loginTimeout; } /** {@inheritDoc} */ public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } /** {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public T unwrap(Class iface) throws SQLException { if (iface.isInstance(this)) { return (T) this; } throw new SQLException("Wrapped DataSource is not an instance of " + iface); } /** {@inheritDoc} */ @Override public boolean isWrapperFor(Class iface) throws SQLException { return false; } /** {@inheritDoc} */ @Override public Connection getConnection() throws SQLException { if (throwException != null) { throw throwException; } if (connectionAcquisitionTime > 0) { UtilityElf.quietlySleep(connectionAcquisitionTime); } return new StubConnection(waitTimeout); } /** {@inheritDoc} */ @Override public Connection getConnection(String username, String password) throws SQLException { return new StubConnection(waitTimeout); } public void setThrowException(SQLException e) { this.throwException = e; } public void setConnectionAcquisitionTime(long connectionAcquisitionTime) { this.connectionAcquisitionTime = connectionAcquisitionTime; } } ================================================ FILE: src/test/java/com/zaxxer/hikari/mocks/StubDriver.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.mocks; import java.sql.Connection; import java.sql.Driver; import java.sql.DriverManager; import java.sql.DriverPropertyInfo; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.Properties; import java.util.logging.Logger; /** * * @author Brett Wooldridge */ public class StubDriver implements Driver { private static final Driver driver; static { driver = new StubDriver(); try { DriverManager.registerDriver(driver); } catch (SQLException e) { e.printStackTrace(); } } /** {@inheritDoc} */ @Override public Connection connect(String url, Properties info) throws SQLException { return new StubConnection(); } /** {@inheritDoc} */ @Override public boolean acceptsURL(String url) throws SQLException { return "jdbc:stub".equals(url); } /** {@inheritDoc} */ @Override public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { return null; } /** {@inheritDoc} */ @Override public int getMajorVersion() { return 0; } /** {@inheritDoc} */ @Override public int getMinorVersion() { return 0; } /** {@inheritDoc} */ @Override public boolean jdbcCompliant() { return true; } /** {@inheritDoc} */ public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } } ================================================ FILE: src/test/java/com/zaxxer/hikari/mocks/StubPoolStats.java ================================================ package com.zaxxer.hikari.mocks; import com.zaxxer.hikari.metrics.PoolStats; public class StubPoolStats extends PoolStats { public StubPoolStats(long timeoutMs) { super(timeoutMs); } @Override protected void update() { // Do nothing } } ================================================ FILE: src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.mocks; import java.io.InputStream; import java.io.Reader; import java.math.BigDecimal; import java.net.URL; import java.sql.Array; import java.sql.Blob; import java.sql.Clob; import java.sql.Connection; import java.sql.Date; import java.sql.NClob; import java.sql.ParameterMetaData; import java.sql.PreparedStatement; import java.sql.Ref; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.RowId; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.SQLXML; import java.sql.Time; import java.sql.Timestamp; import java.util.Calendar; /** * * @author Brett Wooldridge */ @SuppressWarnings("RedundantThrows") public class StubPreparedStatement extends StubStatement implements PreparedStatement { StubPreparedStatement(Connection connection) { super(connection); } /** {@inheritDoc} */ @Override public ResultSet executeQuery(String sql) throws SQLException { return new StubResultSet(); } /** {@inheritDoc} */ @Override public int executeUpdate(String sql) throws SQLException { return 0; } /** {@inheritDoc} */ @Override public int getMaxFieldSize() throws SQLException { throw new SQLException("Simulated disconnection error", "08999"); } /** {@inheritDoc} */ @Override public void setMaxFieldSize(int max) throws SQLException { } /** {@inheritDoc} */ @Override public int getMaxRows() throws SQLException { return 0; } /** {@inheritDoc} */ @Override public void setMaxRows(int max) throws SQLException { } /** {@inheritDoc} */ @Override public void setEscapeProcessing(boolean enable) throws SQLException { } /** {@inheritDoc} */ @Override public int getQueryTimeout() throws SQLException { return 0; } /** {@inheritDoc} */ @Override public void setQueryTimeout(int seconds) throws SQLException { } /** {@inheritDoc} */ @Override public void cancel() throws SQLException { } /** {@inheritDoc} */ @Override public SQLWarning getWarnings() throws SQLException { return null; } /** {@inheritDoc} */ @Override public void clearWarnings() throws SQLException { } /** {@inheritDoc} */ @Override public void setCursorName(String name) throws SQLException { } /** {@inheritDoc} */ @Override public boolean execute(String sql) throws SQLException { return false; } /** {@inheritDoc} */ @Override public ResultSet getResultSet() throws SQLException { return new StubResultSet(); } /** {@inheritDoc} */ @Override public int getUpdateCount() throws SQLException { return 0; } /** {@inheritDoc} */ @Override public boolean getMoreResults() throws SQLException { if (isClosed()) { throw new SQLException("Connection is closed"); } return false; } /** {@inheritDoc} */ @Override public void setFetchDirection(int direction) throws SQLException { } /** {@inheritDoc} */ @Override public int getFetchDirection() throws SQLException { return 0; } /** {@inheritDoc} */ @Override public void setFetchSize(int rows) throws SQLException { } /** {@inheritDoc} */ @Override public int getFetchSize() throws SQLException { return 0; } /** {@inheritDoc} */ @Override public int getResultSetConcurrency() throws SQLException { return 0; } /** {@inheritDoc} */ @Override public int getResultSetType() throws SQLException { return 0; } /** {@inheritDoc} */ @Override public void addBatch(String sql) throws SQLException { } /** {@inheritDoc} */ @Override public void clearBatch() throws SQLException { } /** {@inheritDoc} */ @Override public int[] executeBatch() throws SQLException { return null; } /** {@inheritDoc} */ @Override public boolean getMoreResults(int current) throws SQLException { return false; } /** {@inheritDoc} */ @Override public ResultSet getGeneratedKeys() throws SQLException { return new StubResultSet(); } /** {@inheritDoc} */ @Override public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { return 0; } /** {@inheritDoc} */ @Override public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { return 0; } /** {@inheritDoc} */ @Override public int executeUpdate(String sql, String[] columnNames) throws SQLException { return 0; } /** {@inheritDoc} */ @Override public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { return false; } /** {@inheritDoc} */ @Override public boolean execute(String sql, int[] columnIndexes) throws SQLException { return false; } /** {@inheritDoc} */ @Override public boolean execute(String sql, String[] columnNames) throws SQLException { return false; } /** {@inheritDoc} */ @Override public int getResultSetHoldability() throws SQLException { return 0; } /** {@inheritDoc} */ @Override public void setPoolable(boolean poolable) throws SQLException { } /** {@inheritDoc} */ @Override public boolean isPoolable() throws SQLException { return false; } /** {@inheritDoc} */ @Override public void closeOnCompletion() throws SQLException { } /** {@inheritDoc} */ @Override public boolean isCloseOnCompletion() throws SQLException { return false; } /** {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public T unwrap(Class iface) throws SQLException { if (iface.isInstance(this)) { return (T) this; } throw new SQLException("Wrapped connection is not an instance of " + iface); } /** {@inheritDoc} */ @Override public boolean isWrapperFor(Class iface) throws SQLException { return false; } /** {@inheritDoc} */ @Override public ResultSet executeQuery() throws SQLException { return new StubResultSet(); } /** {@inheritDoc} */ @Override public int executeUpdate() throws SQLException { return 0; } /** {@inheritDoc} */ @Override public void setNull(int parameterIndex, int sqlType) throws SQLException { } /** {@inheritDoc} */ @Override public void setBoolean(int parameterIndex, boolean x) throws SQLException { } /** {@inheritDoc} */ @Override public void setByte(int parameterIndex, byte x) throws SQLException { } /** {@inheritDoc} */ @Override public void setShort(int parameterIndex, short x) throws SQLException { } /** {@inheritDoc} */ @Override public void setInt(int parameterIndex, int x) throws SQLException { } /** {@inheritDoc} */ @Override public void setLong(int parameterIndex, long x) throws SQLException { } /** {@inheritDoc} */ @Override public void setFloat(int parameterIndex, float x) throws SQLException { } /** {@inheritDoc} */ @Override public void setDouble(int parameterIndex, double x) throws SQLException { } /** {@inheritDoc} */ @Override public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { } /** {@inheritDoc} */ @Override public void setString(int parameterIndex, String x) throws SQLException { } /** {@inheritDoc} */ @Override public void setBytes(int parameterIndex, byte[] x) throws SQLException { } /** {@inheritDoc} */ @Override public void setDate(int parameterIndex, Date x) throws SQLException { } /** {@inheritDoc} */ @Override public void setTime(int parameterIndex, Time x) throws SQLException { } /** {@inheritDoc} */ @Override public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { } /** {@inheritDoc} */ @Override public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { } /** {@inheritDoc} */ @Override @SuppressWarnings("deprecation") public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { } /** {@inheritDoc} */ @Override public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { } /** {@inheritDoc} */ @Override public void clearParameters() throws SQLException { } /** {@inheritDoc} */ @Override public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException { } /** {@inheritDoc} */ @Override public void setObject(int parameterIndex, Object x) throws SQLException { } /** {@inheritDoc} */ @Override public boolean execute() throws SQLException { return false; } /** {@inheritDoc} */ @Override public void addBatch() throws SQLException { } /** {@inheritDoc} */ @Override public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { } /** {@inheritDoc} */ @Override public void setRef(int parameterIndex, Ref x) throws SQLException { } /** {@inheritDoc} */ @Override public void setBlob(int parameterIndex, Blob x) throws SQLException { } /** {@inheritDoc} */ @Override public void setClob(int parameterIndex, Clob x) throws SQLException { } /** {@inheritDoc} */ @Override public void setArray(int parameterIndex, Array x) throws SQLException { } /** {@inheritDoc} */ @Override public ResultSetMetaData getMetaData() throws SQLException { return null; } /** {@inheritDoc} */ @Override public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { } /** {@inheritDoc} */ @Override public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { } /** {@inheritDoc} */ @Override public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { } /** {@inheritDoc} */ @Override public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { } /** {@inheritDoc} */ @Override public void setURL(int parameterIndex, URL x) throws SQLException { } /** {@inheritDoc} */ @Override public ParameterMetaData getParameterMetaData() throws SQLException { return null; } /** {@inheritDoc} */ @Override public void setRowId(int parameterIndex, RowId x) throws SQLException { } /** {@inheritDoc} */ @Override public void setNString(int parameterIndex, String value) throws SQLException { } /** {@inheritDoc} */ @Override public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void setNClob(int parameterIndex, NClob value) throws SQLException { } /** {@inheritDoc} */ @Override public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { } /** {@inheritDoc} */ @Override public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException { } /** {@inheritDoc} */ @Override public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { } /** {@inheritDoc} */ @Override public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { } /** {@inheritDoc} */ @Override public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { } /** {@inheritDoc} */ @Override public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { } /** {@inheritDoc} */ @Override public void setClob(int parameterIndex, Reader reader) throws SQLException { } /** {@inheritDoc} */ @Override public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { } /** {@inheritDoc} */ @Override public void setNClob(int parameterIndex, Reader reader) throws SQLException { } } ================================================ FILE: src/test/java/com/zaxxer/hikari/mocks/StubResultSet.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.mocks; import java.io.InputStream; import java.io.Reader; import java.math.BigDecimal; import java.net.URL; import java.sql.Array; import java.sql.Blob; import java.sql.Clob; import java.sql.Date; import java.sql.NClob; import java.sql.Ref; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.RowId; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.SQLXML; import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; import java.util.Calendar; import java.util.Map; /** * * @author Brett Wooldridge */ @SuppressWarnings("RedundantThrows") public class StubResultSet implements ResultSet { private int counter; private boolean closed; /** {@inheritDoc} */ @Override public T unwrap(Class iface) throws SQLException { return null; } /** {@inheritDoc} */ @Override public boolean isWrapperFor(Class iface) throws SQLException { return false; } /** {@inheritDoc} */ @Override public boolean next() throws SQLException { return (counter > 100000); } /** {@inheritDoc} */ @Override public void close() throws SQLException { closed = true; } /** {@inheritDoc} */ @Override public boolean wasNull() throws SQLException { return false; } /** {@inheritDoc} */ @Override public String getString(int columnIndex) throws SQLException { return "aString"; } /** {@inheritDoc} */ @Override public boolean getBoolean(int columnIndex) throws SQLException { return false; } /** {@inheritDoc} */ @Override public byte getByte(int columnIndex) throws SQLException { return 0; } /** {@inheritDoc} */ @Override public short getShort(int columnIndex) throws SQLException { return 0; } /** {@inheritDoc} */ @Override public int getInt(int columnIndex) throws SQLException { return ++counter; } /** {@inheritDoc} */ @Override public long getLong(int columnIndex) throws SQLException { return 0; } /** {@inheritDoc} */ @Override public float getFloat(int columnIndex) throws SQLException { throw new SQLException("Simulated disconnection error", "08999"); } /** {@inheritDoc} */ @Override public double getDouble(int columnIndex) throws SQLException { return 0; } /** {@inheritDoc} */ @Override @SuppressWarnings("deprecation") public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException { return null; } /** {@inheritDoc} */ @Override public byte[] getBytes(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Date getDate(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Time getTime(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Timestamp getTimestamp(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public InputStream getAsciiStream(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override @SuppressWarnings("deprecation") public InputStream getUnicodeStream(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public InputStream getBinaryStream(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public String getString(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public boolean getBoolean(String columnLabel) throws SQLException { return false; } /** {@inheritDoc} */ @Override public byte getByte(String columnLabel) throws SQLException { return 0; } /** {@inheritDoc} */ @Override public short getShort(String columnLabel) throws SQLException { return 0; } /** {@inheritDoc} */ @Override public int getInt(String columnLabel) throws SQLException { return 0; } /** {@inheritDoc} */ @Override public long getLong(String columnLabel) throws SQLException { return 0; } /** {@inheritDoc} */ @Override public float getFloat(String columnLabel) throws SQLException { return 0; } /** {@inheritDoc} */ @Override public double getDouble(String columnLabel) throws SQLException { return 0; } /** {@inheritDoc} */ @Override @SuppressWarnings("deprecation") public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException { return null; } /** {@inheritDoc} */ @Override public byte[] getBytes(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Date getDate(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Time getTime(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Timestamp getTimestamp(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public InputStream getAsciiStream(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override @SuppressWarnings("deprecation") public InputStream getUnicodeStream(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public InputStream getBinaryStream(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public SQLWarning getWarnings() throws SQLException { return null; } /** {@inheritDoc} */ @Override public void clearWarnings() throws SQLException { } /** {@inheritDoc} */ @Override public String getCursorName() throws SQLException { return null; } /** {@inheritDoc} */ @Override public ResultSetMetaData getMetaData() throws SQLException { return null; } /** {@inheritDoc} */ @Override public Object getObject(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Object getObject(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public int findColumn(String columnLabel) throws SQLException { return 0; } /** {@inheritDoc} */ @Override public Reader getCharacterStream(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Reader getCharacterStream(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public BigDecimal getBigDecimal(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public BigDecimal getBigDecimal(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public boolean isBeforeFirst() throws SQLException { return false; } /** {@inheritDoc} */ @Override public boolean isAfterLast() throws SQLException { return false; } /** {@inheritDoc} */ @Override public boolean isFirst() throws SQLException { return false; } /** {@inheritDoc} */ @Override public boolean isLast() throws SQLException { return false; } /** {@inheritDoc} */ @Override public void beforeFirst() throws SQLException { } /** {@inheritDoc} */ @Override public void afterLast() throws SQLException { } /** {@inheritDoc} */ @Override public boolean first() throws SQLException { return false; } /** {@inheritDoc} */ @Override public boolean last() throws SQLException { return false; } /** {@inheritDoc} */ @Override public int getRow() throws SQLException { return 0; } /** {@inheritDoc} */ @Override public boolean absolute(int row) throws SQLException { return false; } /** {@inheritDoc} */ @Override public boolean relative(int rows) throws SQLException { return false; } /** {@inheritDoc} */ @Override public boolean previous() throws SQLException { return false; } /** {@inheritDoc} */ @Override public void setFetchDirection(int direction) throws SQLException { } /** {@inheritDoc} */ @Override public int getFetchDirection() throws SQLException { return 0; } /** {@inheritDoc} */ @Override public void setFetchSize(int rows) throws SQLException { } /** {@inheritDoc} */ @Override public int getFetchSize() throws SQLException { return 0; } /** {@inheritDoc} */ @Override public int getType() throws SQLException { return 0; } /** {@inheritDoc} */ @Override public int getConcurrency() throws SQLException { return 0; } /** {@inheritDoc} */ @Override public boolean rowUpdated() throws SQLException { return false; } /** {@inheritDoc} */ @Override public boolean rowInserted() throws SQLException { return false; } /** {@inheritDoc} */ @Override public boolean rowDeleted() throws SQLException { return false; } /** {@inheritDoc} */ @Override public void updateNull(int columnIndex) throws SQLException { } /** {@inheritDoc} */ @Override public void updateBoolean(int columnIndex, boolean x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateByte(int columnIndex, byte x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateShort(int columnIndex, short x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateInt(int columnIndex, int x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateLong(int columnIndex, long x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateFloat(int columnIndex, float x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateDouble(int columnIndex, double x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateString(int columnIndex, String x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateBytes(int columnIndex, byte[] x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateDate(int columnIndex, Date x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateTime(int columnIndex, Time x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException { } /** {@inheritDoc} */ @Override public void updateObject(int columnIndex, Object x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateNull(String columnLabel) throws SQLException { } /** {@inheritDoc} */ @Override public void updateBoolean(String columnLabel, boolean x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateByte(String columnLabel, byte x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateShort(String columnLabel, short x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateInt(String columnLabel, int x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateLong(String columnLabel, long x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateFloat(String columnLabel, float x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateDouble(String columnLabel, double x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateString(String columnLabel, String x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateBytes(String columnLabel, byte[] x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateDate(String columnLabel, Date x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateTime(String columnLabel, Time x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException { } /** {@inheritDoc} */ @Override public void updateObject(String columnLabel, Object x) throws SQLException { } /** {@inheritDoc} */ @Override public void insertRow() throws SQLException { } /** {@inheritDoc} */ @Override public void updateRow() throws SQLException { } /** {@inheritDoc} */ @Override public void deleteRow() throws SQLException { } /** {@inheritDoc} */ @Override public void refreshRow() throws SQLException { } /** {@inheritDoc} */ @Override public void cancelRowUpdates() throws SQLException { } /** {@inheritDoc} */ @Override public void moveToInsertRow() throws SQLException { } /** {@inheritDoc} */ @Override public void moveToCurrentRow() throws SQLException { } /** {@inheritDoc} */ @Override public Statement getStatement() throws SQLException { return null; } /** {@inheritDoc} */ @Override public Object getObject(int columnIndex, Map> map) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Ref getRef(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Blob getBlob(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Clob getClob(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Array getArray(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Object getObject(String columnLabel, Map> map) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Ref getRef(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Blob getBlob(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Clob getClob(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Array getArray(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Date getDate(int columnIndex, Calendar cal) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Date getDate(String columnLabel, Calendar cal) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Time getTime(int columnIndex, Calendar cal) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Time getTime(String columnLabel, Calendar cal) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException { return null; } /** {@inheritDoc} */ @Override public URL getURL(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public URL getURL(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public void updateRef(int columnIndex, Ref x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateRef(String columnLabel, Ref x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateBlob(int columnIndex, Blob x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateBlob(String columnLabel, Blob x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateClob(int columnIndex, Clob x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateClob(String columnLabel, Clob x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateArray(int columnIndex, Array x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateArray(String columnLabel, Array x) throws SQLException { } /** {@inheritDoc} */ @Override public RowId getRowId(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public RowId getRowId(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public void updateRowId(int columnIndex, RowId x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateRowId(String columnLabel, RowId x) throws SQLException { } /** {@inheritDoc} */ @Override public int getHoldability() throws SQLException { return 0; } /** {@inheritDoc} */ @Override public boolean isClosed() throws SQLException { return closed; } /** {@inheritDoc} */ @Override public void updateNString(int columnIndex, String nString) throws SQLException { } /** {@inheritDoc} */ @Override public void updateNString(String columnLabel, String nString) throws SQLException { } /** {@inheritDoc} */ @Override public void updateNClob(int columnIndex, NClob nClob) throws SQLException { } /** {@inheritDoc} */ @Override public void updateNClob(String columnLabel, NClob nClob) throws SQLException { } /** {@inheritDoc} */ @Override public NClob getNClob(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public NClob getNClob(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public SQLXML getSQLXML(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public SQLXML getSQLXML(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException { } /** {@inheritDoc} */ @Override public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException { } /** {@inheritDoc} */ @Override public String getNString(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public String getNString(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Reader getNCharacterStream(int columnIndex) throws SQLException { return null; } /** {@inheritDoc} */ @Override public Reader getNCharacterStream(String columnLabel) throws SQLException { return null; } /** {@inheritDoc} */ @Override public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateClob(int columnIndex, Reader reader, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateClob(String columnLabel, Reader reader, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException { } /** {@inheritDoc} */ @Override public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException { } /** {@inheritDoc} */ @Override public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateCharacterStream(int columnIndex, Reader x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException { } /** {@inheritDoc} */ @Override public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException { } /** {@inheritDoc} */ @Override public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException { } /** {@inheritDoc} */ @Override public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException { } /** {@inheritDoc} */ @Override public void updateClob(int columnIndex, Reader reader) throws SQLException { } /** {@inheritDoc} */ @Override public void updateClob(String columnLabel, Reader reader) throws SQLException { } /** {@inheritDoc} */ @Override public void updateNClob(int columnIndex, Reader reader) throws SQLException { } /** {@inheritDoc} */ @Override public void updateNClob(String columnLabel, Reader reader) throws SQLException { } /** {@inheritDoc} */ public T getObject(int columnIndex, Class type) throws SQLException { return null; } /** {@inheritDoc} */ public T getObject(String columnLabel, Class type) throws SQLException { return null; } } ================================================ FILE: src/test/java/com/zaxxer/hikari/mocks/StubStatement.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.mocks; import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.SQLWarning; import java.sql.Statement; /** * * @author Brett Wooldridge */ public class StubStatement implements Statement { public static volatile boolean oldDriver; private static volatile long simulatedQueryTime; private boolean closed; private Connection connection; public StubStatement(Connection connection) { this.connection = connection; } public static void setSimulatedQueryTime(long time) { simulatedQueryTime = time; } /** {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public T unwrap(Class iface) throws SQLException { checkClosed(); return (T) this; } /** {@inheritDoc} */ @Override public boolean isWrapperFor(Class iface) throws SQLException { checkClosed(); return false; } /** {@inheritDoc} */ @Override public ResultSet executeQuery(String sql) throws SQLException { checkClosed(); StubResultSet resultSet = new StubResultSet(); connection.commit(); return resultSet; } /** {@inheritDoc} */ @Override public int executeUpdate(String sql) throws SQLException { checkClosed(); return 0; } /** {@inheritDoc} */ @Override public void close() throws SQLException { closed = true; } /** {@inheritDoc} */ @Override public int getMaxFieldSize() throws SQLException { checkClosed(); return 0; } /** {@inheritDoc} */ @Override public void setMaxFieldSize(int max) throws SQLException { checkClosed(); } /** {@inheritDoc} */ @Override public int getMaxRows() throws SQLException { checkClosed(); return 0; } /** {@inheritDoc} */ @Override public void setMaxRows(int max) throws SQLException { checkClosed(); } /** {@inheritDoc} */ @Override public void setEscapeProcessing(boolean enable) throws SQLException { checkClosed(); } /** {@inheritDoc} */ @Override public int getQueryTimeout() throws SQLException { checkClosed(); return 0; } /** {@inheritDoc} */ @Override public void setQueryTimeout(int seconds) throws SQLException { if (oldDriver) { throw new SQLFeatureNotSupportedException(); } checkClosed(); } /** {@inheritDoc} */ @Override public void cancel() throws SQLException { checkClosed(); } /** {@inheritDoc} */ @Override public SQLWarning getWarnings() throws SQLException { checkClosed(); return null; } /** {@inheritDoc} */ @Override public void clearWarnings() throws SQLException { checkClosed(); } /** {@inheritDoc} */ @Override public void setCursorName(String name) throws SQLException { checkClosed(); } /** {@inheritDoc} */ @Override public boolean execute(String sql) throws SQLException { checkClosed(); if (simulatedQueryTime > 0) { quietlySleep(simulatedQueryTime); } connection.commit(); return false; } /** {@inheritDoc} */ @Override public ResultSet getResultSet() throws SQLException { checkClosed(); return new StubResultSet(); } /** {@inheritDoc} */ @Override public int getUpdateCount() throws SQLException { checkClosed(); return 0; } /** {@inheritDoc} */ @Override public boolean getMoreResults() throws SQLException { checkClosed(); return false; } /** {@inheritDoc} */ @Override public void setFetchDirection(int direction) throws SQLException { checkClosed(); } /** {@inheritDoc} */ @Override public int getFetchDirection() throws SQLException { checkClosed(); return 0; } /** {@inheritDoc} */ @Override public void setFetchSize(int rows) throws SQLException { checkClosed(); } /** {@inheritDoc} */ @Override public int getFetchSize() throws SQLException { checkClosed(); return 0; } /** {@inheritDoc} */ @Override public int getResultSetConcurrency() throws SQLException { checkClosed(); return 0; } /** {@inheritDoc} */ @Override public int getResultSetType() throws SQLException { checkClosed(); return 0; } /** {@inheritDoc} */ @Override public void addBatch(String sql) throws SQLException { checkClosed(); } /** {@inheritDoc} */ @Override public void clearBatch() throws SQLException { checkClosed(); } /** {@inheritDoc} */ @Override public int[] executeBatch() throws SQLException { checkClosed(); return null; } /** {@inheritDoc} */ @Override public Connection getConnection() throws SQLException { checkClosed(); return connection; } /** {@inheritDoc} */ @Override public boolean getMoreResults(int current) throws SQLException { checkClosed(); return false; } /** {@inheritDoc} */ @Override public ResultSet getGeneratedKeys() throws SQLException { checkClosed(); return new StubResultSet(); } /** {@inheritDoc} */ @Override public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { checkClosed(); return 0; } /** {@inheritDoc} */ @Override public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { checkClosed(); return 0; } /** {@inheritDoc} */ @Override public int executeUpdate(String sql, String[] columnNames) throws SQLException { checkClosed(); return 0; } /** {@inheritDoc} */ @Override public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { checkClosed(); return false; } /** {@inheritDoc} */ @Override public boolean execute(String sql, int[] columnIndexes) throws SQLException { checkClosed(); return false; } /** {@inheritDoc} */ @Override public boolean execute(String sql, String[] columnNames) throws SQLException { checkClosed(); return false; } /** {@inheritDoc} */ @Override public int getResultSetHoldability() throws SQLException { checkClosed(); return 0; } /** {@inheritDoc} */ @Override public boolean isClosed() throws SQLException { return closed; } /** {@inheritDoc} */ @Override public void setPoolable(boolean poolable) throws SQLException { checkClosed(); } /** {@inheritDoc} */ @Override public boolean isPoolable() throws SQLException { checkClosed(); return false; } /** {@inheritDoc} */ public void closeOnCompletion() throws SQLException { checkClosed(); } /** {@inheritDoc} */ public boolean isCloseOnCompletion() throws SQLException { checkClosed(); return false; } private void checkClosed() throws SQLException { if (closed) { throw new SQLException("Statement is closed"); } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/mocks/TestObject.java ================================================ package com.zaxxer.hikari.mocks; public class TestObject { private TestObject testObject; private String string; private short shortRaw; private char[] charArray; private String[] stringArray; private int[] intArray; public void setTestObject(TestObject testObject) { this.testObject = testObject; } public void setString(String string) { this.string = string; } public TestObject getTestObject() { return testObject; } public String getString() { return string; } public short getShortRaw() { return shortRaw; } public void setShortRaw(short shortRaw) { this.shortRaw = shortRaw; } public void setCharArray(char[] charArray) { this.charArray = charArray; } public char[] getCharArray() { return charArray; } public void setStringArray(String[] stringArray) { this.stringArray = stringArray; } public String[] getStringArray() { return stringArray; } public void setIntArray(int[] intArray) { this.intArray = intArray; } public int[] getIntArray() { return intArray; } } ================================================ FILE: src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.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; 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.isJava11; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.ops4j.pax.exam.CoreOptions.*; /** * @author lburgazzoli */ @RunWith(OSGiBundleTest.ConditionalPaxExam.class) public class OSGiBundleTest { @Test public void checkInject() { assertNotNull(context); } @Test public void checkBundle() { Boolean bundleFound = false; Boolean bundleActive = false; 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; } } } } assertTrue(bundleFound); assertTrue(bundleActive); } @Inject BundleContext context; @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.36"), mavenBundle("org.slf4j", "slf4j-simple", "1.7.36").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 (!isJava11()) { super.run(notifier); } } @Override public void filter(Filter filter) throws NoTestsRemainException { if (isJava11()) { throw new NoTestsRemainException(); } super.filter(filter); } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/CodahaleMetricsTest.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import java.sql.Connection; import java.sql.SQLException; import java.util.SortedMap; import java.util.concurrent.TimeUnit; import com.codahale.metrics.Histogram; import com.codahale.metrics.Metric; import com.codahale.metrics.MetricFilter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; import com.codahale.metrics.health.HealthCheck; import com.codahale.metrics.health.HealthCheckRegistry; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.metrics.MetricsTrackerFactory; import com.zaxxer.hikari.metrics.dropwizard.CodahaleMetricsTrackerFactory; import com.zaxxer.hikari.util.UtilityElf; import org.junit.Test; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource; import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; /** * Test HikariCP/CodaHale metrics integration. * * @author Brett Wooldridge */ public class CodahaleMetricsTest extends TestMetricsBase { @Override protected MetricsTrackerFactory metricsTrackerFactory(final MetricRegistry metricRegistry) { return new CodahaleMetricsTrackerFactory(metricRegistry); } @Override protected MetricRegistry metricRegistry() { return new MetricRegistry(); } @Test public void testHealthChecks() throws Exception { MetricRegistry metricRegistry = metricRegistry(); HealthCheckRegistry healthRegistry = new HealthCheckRegistry(); HikariConfig config = newHikariConfig(); config.setMaximumPoolSize(10); config.setMetricRegistry(metricRegistry); config.setHealthCheckRegistry(healthRegistry); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); config.addHealthCheckProperty("connectivityCheckTimeoutMs", "1000"); config.addHealthCheckProperty("expected99thPercentileMs", "100"); try (HikariDataSource ds = new HikariDataSource(config)) { quietlySleep(TimeUnit.SECONDS.toMillis(2)); try (Connection connection = ds.getConnection()) { // close immediately } try (Connection connection = ds.getConnection()) { // close immediately } SortedMap healthChecks = healthRegistry.runHealthChecks(); HealthCheck.Result connectivityResult = healthChecks.get("testHealthChecks.pool.ConnectivityCheck"); assertTrue(connectivityResult.isHealthy()); HealthCheck.Result slaResult = healthChecks.get("testHealthChecks.pool.Connection99Percent"); assertTrue(slaResult.isHealthy()); } } @Test public void testSetters1() throws Exception { try (HikariDataSource ds = newHikariDataSource()) { ds.setMaximumPoolSize(1); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); MetricRegistry metricRegistry = 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"); } catch (IllegalStateException ise) { // pass try { ds.setHealthCheckRegistry(healthRegistry); fail("Should not have been allowed to set registry after pool started"); } catch (IllegalStateException ise2) { // pass } } } } @Test public void testSetters2() throws Exception { try (HikariDataSource ds = newHikariDataSource()) { ds.setMaximumPoolSize(1); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); MetricRegistry metricRegistry = 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"); } catch (IllegalStateException ise) { // pass } } } @Test public void testMetricWait() throws SQLException { MetricRegistry metricRegistry = new MetricRegistry(); HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(1); config.setMetricRegistry(metricRegistry); config.setInitializationFailTimeout(Long.MAX_VALUE); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { ds.getConnection().close(); Timer timer = metricRegistry.getTimers(new MetricFilter() { /** {@inheritDoc} */ @Override public boolean matches(String name, Metric metric) { return name.equals(MetricRegistry.name("testMetricWait", "pool", "Wait")); } }).values().iterator().next(); assertEquals(1, timer.getCount()); assertTrue(timer.getMeanRate() > 0.0); } } @Test public void testMetricUsage() throws SQLException { assumeFalse(System.getProperty("os.name").contains("Windows")); MetricRegistry metricRegistry = new MetricRegistry(); HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(1); config.setMetricRegistry(metricRegistry); config.setInitializationFailTimeout(0); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { try (Connection connection = ds.getConnection()) { UtilityElf.quietlySleep(250L); } Histogram histo = metricRegistry.getHistograms(new MetricFilter() { /** {@inheritDoc} */ @Override public boolean matches(String name, Metric metric) { return name.equals(MetricRegistry.name("testMetricUsage", "pool", "Usage")); } }).values().iterator().next(); assertEquals(1, histo.getCount()); double seventyFifth = histo.getSnapshot().get75thPercentile(); assertTrue("Seventy-fith percentile less than 250ms: " + seventyFifth, seventyFifth >= 250.0); } } @Test public void testMetricRegistrySubclassIsAllowed() { try (HikariDataSource ds = newHikariDataSource()) { ds.setMaximumPoolSize(1); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); ds.setMetricRegistry(new MetricRegistry() { @Override public Timer timer(String name) { return super.timer(name); } }); } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/ConcurrentCloseConnectionTest.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import java.sql.Connection; import java.sql.PreparedStatement; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import org.junit.Test; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; /** * @author Matthew Tambara (matthew.tambara@liferay.com) */ public class ConcurrentCloseConnectionTest { @Test public void testConcurrentClose() throws Exception { HikariConfig config = newHikariConfig(); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config); final Connection connection = ds.getConnection()) { ExecutorService executorService = Executors.newFixedThreadPool(10); List> futures = new ArrayList<>(); for (int i = 0; i < 500; i++) { final PreparedStatement preparedStatement = connection.prepareStatement(""); futures.add(executorService.submit(new Callable() { @Override public Void call() throws Exception { preparedStatement.close(); return null; } })); } executorService.shutdown(); for (Future future : futures) { future.get(); } } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java ================================================ /* * Copyright (C) 2013, 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.getPool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; 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.util.concurrent.Executors.newFixedThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.sql.Connection; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicReference; 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.StubDataSource; /** * @author Matthew Tambara (matthew.tambara@liferay.com) */ public class ConnectionPoolSizeVsThreadsTest { private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionPoolSizeVsThreadsTest.class); private static final int ITERATIONS = 50_000; @Test public void testPoolSizeAboutSameSizeAsThreadCount() throws Exception { final int threadCount = 50; final Counts counts = testPoolSize(2 /*minIdle*/, 100 /*maxPoolSize*/, threadCount, 1 /*workTimeMs*/, 0 /*restTimeMs*/, 20 /*connectionAcquisitionTimeMs*/, ITERATIONS, SECONDS.toMillis(2) /*postTestTimeMs*/); // maxActive may never make it to threadCount but it shouldn't be any higher assertEquals(threadCount, counts.maxActive, 15 /*delta*/); assertEquals(threadCount, counts.maxTotal, 5 /*delta*/); } @Test public void testSlowConnectionTimeBurstyWork() throws Exception { // setup a bursty work load, 50 threads all needing to do around 100 units of work. // Using a more realistic time for connection startup of 250 ms and only 5 seconds worth of work will mean that we end up finishing // all of the work before we actually have setup 50 connections even though we have requested 50 connections final int threadCount = 50; final int workItems = threadCount * 100; final int workTimeMs = 0; final int connectionAcquisitionTimeMs = 250; final Counts counts = testPoolSize(2 /*minIdle*/, 100 /*maxPoolSize*/, threadCount, workTimeMs, 0 /*restTimeMs*/, connectionAcquisitionTimeMs, workItems /*iterations*/, SECONDS.toMillis(3) /*postTestTimeMs*/); // hard to put exact bounds on how many thread we will use but we can put an upper bound on usage (if there was only one thread) final long totalWorkTime = workItems * workTimeMs; final long connectionMax = totalWorkTime / connectionAcquisitionTimeMs; assertTrue(connectionMax <= counts.maxActive); assertEquals(connectionMax, counts.maxTotal, 2 + 2 /*delta*/); } private Counts testPoolSize(final int minIdle, final int maxPoolSize, final int threadCount, final long workTimeMs, final long restTimeMs, final long connectionAcquisitionTimeMs, final int iterations, final long postTestTimeMs) throws Exception { LOGGER.info("Starting test (minIdle={}, maxPoolSize={}, threadCount={}, workTimeMs={}, restTimeMs={}, connectionAcquisitionTimeMs={}, iterations={}, postTestTimeMs={})", minIdle, maxPoolSize, threadCount, workTimeMs, restTimeMs, connectionAcquisitionTimeMs, iterations, postTestTimeMs); final HikariConfig config = newHikariConfig(); config.setMinimumIdle(minIdle); config.setMaximumPoolSize(maxPoolSize); config.setInitializationFailTimeout(Long.MAX_VALUE); config.setConnectionTimeout(2500); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); final AtomicReference ref = new AtomicReference<>(null); // Initialize HikariPool with no initial connections and room to grow try (final HikariDataSource ds = new HikariDataSource(config)) { final StubDataSource stubDataSource = ds.unwrap(StubDataSource.class); // connection acquisition takes more than 0 ms in a real system stubDataSource.setConnectionAcquisitionTime(connectionAcquisitionTimeMs); final ExecutorService threadPool = newFixedThreadPool(threadCount); final CountDownLatch allThreadsDone = new CountDownLatch(iterations); for (int i = 0; i < iterations; i++) { threadPool.submit(() -> { if (ref.get() == null) { quietlySleep(restTimeMs); try (Connection c2 = ds.getConnection()) { quietlySleep(workTimeMs); } catch (Exception e) { ref.set(e); } } allThreadsDone.countDown(); }); } final HikariPool pool = getPool(ds); // collect pool usage data while work is still being done final Counts underLoad = new Counts(); while (allThreadsDone.getCount() > 0 || pool.getTotalConnections() < minIdle) { quietlySleep(50); underLoad.updateMaxCounts(pool); } // wait for long enough any pending acquisitions have already been done LOGGER.info("Test Over, waiting for post delay time {}ms ", postTestTimeMs); quietlySleep(connectionAcquisitionTimeMs + workTimeMs + restTimeMs); // collect pool data while there is no work to do. final Counts postLoad = new Counts(); final long start = currentTime(); while (elapsedMillis(start) < postTestTimeMs) { quietlySleep(50); postLoad.updateMaxCounts(pool); } allThreadsDone.await(); threadPool.shutdown(); threadPool.awaitTermination(30, SECONDS); if (ref.get() != null) { LOGGER.error("Task failed", ref.get()); fail("Task failed"); } LOGGER.info("Under Load... {}", underLoad); LOGGER.info("Post Load.... {}", postLoad); // verify that the no connections created after the work has stopped if (postTestTimeMs > 0) { if (postLoad.maxActive != 0) { fail("Max Active was greater than 0 after test was done"); } final int createdAfterWorkAllFinished = postLoad.maxTotal - underLoad.maxTotal; assertEquals("Connections were created when there was no waiting consumers", 0, createdAfterWorkAllFinished, 1 /*delta*/); } return underLoad; } } private static class Counts { int maxTotal = 0; int maxActive = 0; void updateMaxCounts(final HikariPool pool) { maxTotal = Math.max(pool.getTotalConnections(), maxTotal); maxActive = Math.max(pool.getActiveConnections(), maxActive); } @Override public String toString() { return "Counts{" + "maxTotal=" + maxTotal + ", maxActive=" + maxActive + '}'; } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.pool.TestElf.setSlf4jLogLevel; import static org.junit.Assert.fail; import java.sql.Connection; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.apache.logging.log4j.Level; import org.junit.After; import org.junit.Test; import org.slf4j.LoggerFactory; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.util.ConcurrentBag; /** * @author Matthew Tambara (matthew.tambara@liferay.com) */ public class ConnectionRaceConditionTest { public static final int ITERATIONS = 10_000; @Test public void testRaceCondition() throws Exception { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(10); config.setInitializationFailTimeout(Long.MAX_VALUE); config.setConnectionTimeout(5000); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); setSlf4jLogLevel(ConcurrentBag.class, Level.INFO); final AtomicReference ref = new AtomicReference<>(null); // Initialize HikariPool with no initial connections and room to grow try (final HikariDataSource ds = new HikariDataSource(config)) { ExecutorService threadPool = Executors.newFixedThreadPool(2); for (int i = 0; i < ITERATIONS; i++) { threadPool.submit(new Callable() { /** {@inheritDoc} */ @Override public Exception call() throws Exception { if (ref.get() == null) { Connection c2; try { c2 = ds.getConnection(); ds.evictConnection(c2); } catch (Exception e) { ref.set(e); } } return null; } }); } threadPool.shutdown(); threadPool.awaitTermination(30, TimeUnit.SECONDS); if (ref.get() != null) { LoggerFactory.getLogger(ConnectionRaceConditionTest.class).error("Task failed", ref.get()); fail("Task failed"); } } catch (Exception e) { throw e; } } @After public void after() { System.getProperties().remove("com.zaxxer.hikari.housekeeping.periodMs"); setSlf4jLogLevel(HikariPool.class, Level.WARN); setSlf4jLogLevel(ConcurrentBag.class, Level.WARN); } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/ConnectionStateTest.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import org.junit.Test; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.util.UtilityElf; public class ConnectionStateTest { @Test public void testAutoCommit() throws SQLException { try (HikariDataSource ds = newHikariDataSource()) { ds.setAutoCommit(true); ds.setMinimumIdle(1); ds.setMaximumPoolSize(1); ds.setConnectionTestQuery("VALUES 1"); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); ds.addDataSourceProperty("user", "bar"); ds.addDataSourceProperty("password", "secret"); ds.addDataSourceProperty("url", "baf"); ds.addDataSourceProperty("loginTimeout", "10"); try (Connection connection = ds.getConnection()) { Connection unwrap = connection.unwrap(Connection.class); connection.setAutoCommit(false); connection.close(); assertTrue(unwrap.getAutoCommit()); } } } @Test public void testTransactionIsolation() throws SQLException { try (HikariDataSource ds = newHikariDataSource()) { ds.setTransactionIsolation("TRANSACTION_READ_COMMITTED"); ds.setMinimumIdle(1); ds.setMaximumPoolSize(1); ds.setConnectionTestQuery("VALUES 1"); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (Connection connection = ds.getConnection()) { Connection unwrap = connection.unwrap(Connection.class); connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); connection.close(); assertEquals(Connection.TRANSACTION_READ_COMMITTED, unwrap.getTransactionIsolation()); } } } @Test public void testIsolation() throws Exception { HikariConfig config = newHikariConfig(); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); config.setTransactionIsolation("TRANSACTION_REPEATABLE_READ"); config.validate(); int transactionIsolation = UtilityElf.getTransactionIsolation(config.getTransactionIsolation()); assertSame(Connection.TRANSACTION_REPEATABLE_READ, transactionIsolation); } @Test public void testReadOnly() throws Exception { try (HikariDataSource ds = newHikariDataSource()) { ds.setCatalog("test"); ds.setMinimumIdle(1); ds.setMaximumPoolSize(1); ds.setConnectionTestQuery("VALUES 1"); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (Connection connection = ds.getConnection()) { Connection unwrap = connection.unwrap(Connection.class); connection.setReadOnly(true); connection.close(); assertFalse(unwrap.isReadOnly()); } } } @Test public void testCatalog() throws SQLException { try (HikariDataSource ds = newHikariDataSource()) { ds.setCatalog("test"); ds.setMinimumIdle(1); ds.setMaximumPoolSize(1); ds.setConnectionTestQuery("VALUES 1"); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (Connection connection = ds.getConnection()) { Connection unwrap = connection.unwrap(Connection.class); connection.setCatalog("other"); connection.close(); assertEquals("test", unwrap.getCatalog()); } } } @Test public void testCommitTracking() throws SQLException { try (HikariDataSource ds = newHikariDataSource()) { ds.setAutoCommit(false); ds.setMinimumIdle(1); ds.setMaximumPoolSize(1); ds.setConnectionTestQuery("VALUES 1"); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (Connection connection = ds.getConnection()) { Statement statement = connection.createStatement(); statement.execute("SELECT something"); assertTrue(TestElf.getConnectionCommitDirtyState(connection)); connection.commit(); assertFalse(TestElf.getConnectionCommitDirtyState(connection)); statement.execute("SELECT something", Statement.NO_GENERATED_KEYS); assertTrue(TestElf.getConnectionCommitDirtyState(connection)); connection.rollback(); assertFalse(TestElf.getConnectionCommitDirtyState(connection)); ResultSet resultSet = statement.executeQuery("SELECT something"); assertTrue(TestElf.getConnectionCommitDirtyState(connection)); connection.rollback(null); assertTrue(TestElf.getConnectionCommitDirtyState(connection)); resultSet.updateRow(); assertTrue(TestElf.getConnectionCommitDirtyState(connection)); connection.setReadOnly(!connection.isReadOnly()); assertTrue(TestElf.getConnectionCommitDirtyState(connection)); } } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/Dropwizard5MetricsTest.java ================================================ package com.zaxxer.hikari.pool; import java.sql.Connection; import java.sql.SQLException; import com.codahale.metrics.health.HealthCheckRegistry; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.metrics.MetricsTrackerFactory; import com.zaxxer.hikari.metrics.dropwizard.Dropwizard5MetricsTrackerFactory; import com.zaxxer.hikari.util.UtilityElf; import io.dropwizard.metrics5.Histogram; import io.dropwizard.metrics5.Metric; import io.dropwizard.metrics5.MetricFilter; import io.dropwizard.metrics5.MetricName; import io.dropwizard.metrics5.MetricRegistry; import io.dropwizard.metrics5.Timer; import org.junit.Test; 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.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; /** * Test HikariCP/Dropwizard 5 metrics integration. */ public class Dropwizard5MetricsTest extends TestMetricsBase { @Override protected MetricsTrackerFactory metricsTrackerFactory(final MetricRegistry metricRegistry) { return new Dropwizard5MetricsTrackerFactory(metricRegistry); } @Override protected MetricRegistry metricRegistry() { return new MetricRegistry(); } @Test public void testSetters1() throws Exception { try (HikariDataSource ds = newHikariDataSource()) { ds.setMaximumPoolSize(1); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); MetricRegistry metricRegistry = metricRegistry(); try { try (Connection connection = ds.getConnection()) { // close immediately } // After the pool as started, we can only set it once... ds.setMetricRegistry(metricRegistry); // and never again... ds.setMetricRegistry(metricRegistry); fail("Should not have been allowed to set registry after pool started"); } catch (IllegalStateException ise) { // pass } } } @Test public void testSetters2() throws Exception { try (HikariDataSource ds = newHikariDataSource()) { ds.setMaximumPoolSize(1); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); MetricRegistry metricRegistry = metricRegistry(); ds.setMetricRegistry(metricRegistry); // before the pool is started, we can set it any number of times... 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"); } catch (IllegalStateException ise) { // pass } } } @Test public void testMetricWait() throws SQLException { MetricRegistry metricRegistry = new MetricRegistry(); HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(1); config.setMetricRegistry(metricRegistry); config.setInitializationFailTimeout(Long.MAX_VALUE); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { ds.getConnection().close(); Timer timer = metricRegistry.getTimers(new MetricFilter() { /** {@inheritDoc} */ @Override public boolean matches(MetricName name, Metric metric) { return name.equals(MetricRegistry.name("testMetricWait", "pool", "Wait")); } }).values().iterator().next(); assertEquals(1, timer.getCount()); assertTrue(timer.getMeanRate() > 0.0); } } @Test public void testMetricUsage() throws SQLException { assumeFalse(System.getProperty("os.name").contains("Windows")); MetricRegistry metricRegistry = new MetricRegistry(); HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(1); config.setMetricRegistry(metricRegistry); config.setInitializationFailTimeout(0); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { try (Connection connection = ds.getConnection()) { UtilityElf.quietlySleep(250L); } Histogram histo = metricRegistry.getHistograms(new MetricFilter() { /** {@inheritDoc} */ @Override public boolean matches(MetricName name, Metric metric) { return name.equals(MetricRegistry.name("testMetricUsage", "pool", "Usage")); } }).values().iterator().next(); assertEquals(1, histo.getCount()); double seventyFifth = histo.getSnapshot().get75thPercentile(); assertTrue("Seventy-fith percentile less than 250ms: " + seventyFifth, seventyFifth >= 250.0); } } @Test public void testMetricRegistrySubclassIsAllowed() { try (HikariDataSource ds = newHikariDataSource()) { ds.setMaximumPoolSize(1); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); ds.setMetricRegistry(new MetricRegistry() { @Override public Timer timer(String name) { return super.timer(name); } }); } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/ExceptionTest.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.pool.TestElf.getPool; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.zaxxer.hikari.mocks.StubConnection; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; public class ExceptionTest { private HikariDataSource ds; private final int CONNECTION_TIMEOUT_SECS = 5; @Before public void setup() { HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(2); config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(CONNECTION_TIMEOUT_SECS)); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); ds = new HikariDataSource(config); } @After public void teardown() { ds.close(); } @Test public void testException1() throws SQLException { try (Connection connection = ds.getConnection()) { assertNotNull(connection); PreparedStatement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?"); assertNotNull(statement); ResultSet resultSet = statement.executeQuery(); assertNotNull(resultSet); try { statement.getMaxFieldSize(); fail(); } catch (Exception e) { assertSame(SQLException.class, e.getClass()); } } HikariPool pool = getPool(ds); assertTrue("Total (3) connections not as expected", pool.getTotalConnections() >= 0); assertTrue("Idle (3) connections not as expected", pool.getIdleConnections() >= 0); } @Test public void testUseAfterStatementClose() throws SQLException { Connection connection = ds.getConnection(); assertNotNull(connection); try (Statement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?")) { statement.close(); statement.getMoreResults(); fail(); } catch (SQLException e) { assertSame("Connection is closed", e.getMessage()); } } @Test public void testUseAfterClose() throws SQLException { try (Connection connection = ds.getConnection()) { assertNotNull(connection); connection.close(); try (Statement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?")) { fail(); } catch (SQLException e) { assertSame("Connection is closed", e.getMessage()); } } } /** * This test verifies that in an isConnectionDead call if an exception is thrown from setNetworkTimeout we will not * call setNetworkTimeout a second time resulting in the exception from second hiding the exception from the first. */ @Test public void testLastErrorTimeout() throws Exception { // take one connection so there is only one left. try (Connection ignored1 = ds.getConnection()) { try (Connection ignored = ds.getConnection()) { // no-op assertTrue(true); } // force the last access of this connection to be older than 'aliveBypassWindowMs' // thus forcing isConnectionDead to be called (and setNetworkTimeout) Thread.sleep(505); AtomicInteger callCount = new AtomicInteger(); StubConnection.networkTimeoutSetter = () -> { Thread.sleep(TimeUnit.SECONDS.toMillis(CONNECTION_TIMEOUT_SECS + 1)); // wait longer than the connection timeout throw new SQLException("Exception " + callCount.incrementAndGet()); }; try (Connection ignored = ds.getConnection()) { fail("getConnection should have failed"); } catch (SQLException e) { e.printStackTrace(); Throwable cause = e.getCause(); assertNotNull(cause); Throwable causeCause = e.getCause(); assertNotNull(causeCause); assertEquals("Exception 1", causeCause.getMessage()); } finally { // Remove the callback so that we don't interfere with any other tests that might run in the same jvm instance. StubConnection.networkTimeoutSetter = null; } assertEquals(1, callCount.get()); } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static org.junit.Assert.assertEquals; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.util.UtilityElf; /** * @author Martin Stříž (striz@raynet.cz) */ public class HouseKeeperCleanupTest { private ScheduledThreadPoolExecutor executor; @Before public void before() throws Exception { ThreadFactory threadFactory = new UtilityElf.DefaultThreadFactory("global housekeeper"); executor = new ScheduledThreadPoolExecutor(1, threadFactory, new ThreadPoolExecutor.DiscardPolicy()); executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); executor.setRemoveOnCancelPolicy(true); } @Test public void testHouseKeeperCleanupWithCustomExecutor() throws Exception { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(10); config.setInitializationFailTimeout(Long.MAX_VALUE); config.setConnectionTimeout(2500); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); config.setScheduledExecutor(executor); HikariConfig config2 = newHikariConfig(); config.copyStateTo(config2); try ( final HikariDataSource ds1 = new HikariDataSource(config); final HikariDataSource ds2 = new HikariDataSource(config2) ) { assertEquals("Scheduled tasks count not as expected, ", 2, executor.getQueue().size()); } assertEquals("Scheduled tasks count not as expected, ", 0, executor.getQueue().size()); } @After public void after() throws Exception { executor.shutdown(); executor.awaitTermination(5, TimeUnit.SECONDS); } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/IsolationTest.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import java.sql.Connection; import java.sql.SQLException; import org.junit.Test; import com.zaxxer.hikari.HikariDataSource; public class IsolationTest { @Test public void testIsolation() throws SQLException { try (HikariDataSource ds = newHikariDataSource()) { ds.setMinimumIdle(1); ds.setMaximumPoolSize(1); ds.setIsolateInternalQueries(true); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (Connection connection = ds.getConnection()) { connection.close(); try (Connection connection2 = ds.getConnection()) { connection2.close(); assertNotSame(connection, connection2); assertSame(connection.unwrap(Connection.class), connection2.unwrap(Connection.class)); } } } } @Test public void testNonIsolation() throws SQLException { try (HikariDataSource ds = newHikariDataSource()) { ds.setMinimumIdle(1); ds.setMaximumPoolSize(1); ds.setIsolateInternalQueries(false); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (Connection connection = ds.getConnection()) { connection.close(); try (Connection connection2 = ds.getConnection()) { connection2.close(); assertSame(connection.unwrap(Connection.class), connection2.unwrap(Connection.class)); } } } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/JdbcDriverTest.java ================================================ /* * Copyright (C) 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.sql.Connection; import java.sql.SQLException; import org.junit.After; import org.junit.Test; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.util.DriverDataSource; public class JdbcDriverTest { private HikariDataSource ds; @After public void teardown() { if (ds != null) { ds.close(); } } @Test public void driverTest1() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(1); config.setConnectionTestQuery("VALUES 1"); config.setDriverClassName("com.zaxxer.hikari.mocks.StubDriver"); config.setJdbcUrl("jdbc:stub"); config.addDataSourceProperty("user", "bart"); config.addDataSourceProperty("password", "simpson"); ds = new HikariDataSource(config); assertTrue(ds.isWrapperFor(DriverDataSource.class)); DriverDataSource unwrap = ds.unwrap(DriverDataSource.class); assertNotNull(unwrap); try (Connection connection = ds.getConnection()) { // test that getConnection() succeeds } } @Test public void driverTest2() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(1); config.setConnectionTestQuery("VALUES 1"); config.setDriverClassName("com.zaxxer.hikari.mocks.StubDriver"); config.setJdbcUrl("jdbc:invalid"); try { ds = new HikariDataSource(config); } catch (RuntimeException e) { assertTrue(e.getMessage().contains("claims to not accept")); } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/MetricsTrackerTest.java ================================================ 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.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; /** * @author wvuong@chariotsolutions.com on 2/16/17. */ public class MetricsTrackerTest { @Test public void connectionTimeoutIsRecorded() throws Exception { assertThrows(SQLTransientConnectionException.class, () -> { int timeoutMillis = 1000; int timeToCreateNewConnectionMillis = timeoutMillis * 2; StubDataSource stubDataSource = new StubDataSource(); stubDataSource.setConnectionAcquisitionTime(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)); metricsTracker.close(); } } }); } @SuppressWarnings("unused") 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; } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/MiscTest.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.pool.TestElf.getPool; import static com.zaxxer.hikari.pool.TestElf.setConfigUnitTest; import static com.zaxxer.hikari.pool.TestElf.setSlf4jLogLevel; import static com.zaxxer.hikari.pool.TestElf.setSlf4jTargetStream; import static com.zaxxer.hikari.util.UtilityElf.createInstance; import static com.zaxxer.hikari.util.UtilityElf.getTransactionIsolation; import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.Level; import org.junit.Test; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; /** * @author Brett Wooldridge */ public class MiscTest { @Test public void testLogWriter() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(4); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); setConfigUnitTest(true); try (HikariDataSource ds = new HikariDataSource(config)) { PrintWriter writer = new PrintWriter(System.out); ds.setLogWriter(writer); assertSame(writer, ds.getLogWriter()); assertEquals("testLogWriter", config.getPoolName()); } finally { setConfigUnitTest(false); } } @Test public void testInvalidIsolation() { try { getTransactionIsolation("INVALID"); fail(); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } } @Test public void testCreateInstance() { try { createInstance("invalid", null); fail(); } catch (RuntimeException e) { assertTrue(e.getCause() instanceof ClassNotFoundException); } } @Test public void testLeakDetection() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); 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); config.setThreadFactory(Executors.defaultThreadFactory()); 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(); quietlySleep(SECONDS.toMillis(1)); ps.close(); String s = new String(baos.toByteArray()); assertNotNull("Exception string was null", s); assertTrue("Expected exception to contain 'Connection leak detection' but contains *" + s + "*", s.contains("Connection leak detection")); } } finally { setConfigUnitTest(false); setSlf4jLogLevel(HikariPool.class, Level.INFO); } } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/PostgresTest.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; 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.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.*; import java.sql.Connection; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import com.zaxxer.hikari.util.Credentials; import org.junit.After; import org.junit.Before; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import org.junit.Test; import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.utility.DockerImageName; public class PostgresTest { private static final DockerImageName IMAGE_NAME = DockerImageName.parse("postgres:16"); private static final String SCHEMA_NAME = "test"; private PostgreSQLContainer postgres; @Before public void beforeTest() { postgres = new PostgreSQLContainer<>(IMAGE_NAME).withInitScript("postgres_init_script.sql"); postgres.start(); } @After public void afterTest() { postgres.stop(); } @Test public void testCase1() throws Exception { HikariConfig config = createConfig(postgres); config.setMinimumIdle(3); config.setMaximumPoolSize(10); config.setConnectionTimeout(3000); config.setIdleTimeout(SECONDS.toMillis(10)); config.setValidationTimeout(SECONDS.toMillis(2)); exerciseConfig(config, 3); } @Test public void testCase2() throws Exception { HikariConfig config = createConfig(postgres); config.setMinimumIdle(3); config.setMaximumPoolSize(10); config.setConnectionTimeout(1000); config.setIdleTimeout(SECONDS.toMillis(20)); exerciseConfig(config, 3); } @Test public void testCase3() throws Exception { HikariConfig config = createConfig(postgres); config.setMinimumIdle(3); config.setMaximumPoolSize(10); config.setConnectionTimeout(1000); config.setIdleTimeout(SECONDS.toMillis(20)); exerciseConfig(config, 3); } @Test public void testCase4() throws Exception { HikariConfig config = createConfig(postgres); config.setMinimumIdle(0); config.setMaximumPoolSize(15); config.setConnectionTimeout(10000); config.setIdleTimeout(2000); config.setMaxLifetime(5); config.setRegisterMbeans(true); exerciseConfig(config, 3); } @Test public void testPgsqlJdbcUrl() { HikariConfig config = newHikariConfig(); config.setJdbcUrl(postgres.getJdbcUrl()); config.setUsername(postgres.getUsername()); config.setPassword(postgres.getPassword()); config.setMinimumIdle(3); config.setMaximumPoolSize(10); config.setConnectionTimeout(1000); config.setIdleTimeout(SECONDS.toMillis(20)); exerciseConfig(config, 3); } @Test public void testCredentialRotation() { HikariConfig config = createConfig(postgres); config.setMinimumIdle(3); config.setMaximumPoolSize(10); config.setConnectionTimeout(1000); config.setIdleTimeout(SECONDS.toMillis(20)); exerciseConfig(config, 3); updatePostgresCredentials("newuser", "newpassword"); config.setJdbcUrl(postgres.getJdbcUrl()); config.setCredentials(Credentials.of("newuser", "newpassword")); exerciseConfig(config, 3); } @Test public void testSchema() throws Exception { HikariConfig config = createConfig(postgres); config.setMinimumIdle(1); config.setMaximumPoolSize(1); config.setSchema(SCHEMA_NAME); try (HikariDataSource ds = new HikariDataSource(config)) { assertTrue(ds.isRunning()); try (Connection connection = ds.getConnection()) { // The connection should be using the schema specified in the config assertEquals(SCHEMA_NAME, connection.getSchema()); // Explicitly set the schema to an invalid schema connection.setSchema("invalid"); // The connection should have a null schema after setting to an invalid schema assertNull(connection.getSchema()); } try (Connection connection = ds.getConnection()) { // The connection should be reset to the schema specified in the config assertEquals(SCHEMA_NAME, connection.getSchema()); } assertTrue(ds.isRunning()); } } static private void exerciseConfig(HikariConfig config, int numThreads) { try (final HikariDataSource ds = new HikariDataSource(config)) { assertTrue(ds.isRunning()); exerciseDataSource(ds, numThreads); assertTrue(ds.isRunning()); } } static private void exerciseDataSource(HikariDataSource ds, int numThreads) { final long start = currentTime(); List threads = startThreads(ds, numThreads); do { quietlySleep(SECONDS.toMillis(1)); assertZeroErrors(threads); } while (elapsedMillis(start) < SECONDS.toMillis(15)); stopThreads(threads); } static private void assertZeroErrors(List threads) { for (PostgresWorkerThread t : threads) { assertEquals(0, t.getErrorCount()); } } static List startThreads(HikariDataSource ds, int numThreads) { List threads = new ArrayList<>(); for (int i = 0; i < numThreads; i++) { PostgresWorkerThread t = new PostgresWorkerThread(ds); t.start(); threads.add(t); } return threads; } static void stopThreads(List threads) { for (PostgresWorkerThread t : threads) { t.requestStop(); } } static class PostgresWorkerThread extends Thread { static private final AtomicInteger id = new AtomicInteger(0); private final HikariDataSource dataSource; private int errorCount = 0; private AtomicBoolean stopRequested = new AtomicBoolean(false); public PostgresWorkerThread(HikariDataSource ds) { this.dataSource = ds; this.setName(getClass().getSimpleName() + "-" + id.getAndIncrement()); this.setDaemon(true); } public void requestStop() { stopRequested.set(true); System.err.println("[" + getName() + "] stopRequested()"); } public void run() { System.err.println("[" + getName() + "] run()"); while (!stopRequested.get()) { try (Connection connection = dataSource.getConnection()) { quietlySleep(5); } catch (Exception e) { e.printStackTrace(); errorCount++; } } } public int getErrorCount() { return errorCount; } } @Before public void before() { System.err.println("\n"); } static private HikariConfig createConfig(PostgreSQLContainer postgres) { HikariConfig config = newHikariConfig(); config.setJdbcUrl(postgres.getJdbcUrl()); config.setUsername(postgres.getUsername()); config.setPassword(postgres.getPassword()); config.setDriverClassName(postgres.getDriverClassName()); return config; } private void updatePostgresCredentials(String username, String password) { postgres.stop(); postgres = new PostgreSQLContainer<>(IMAGE_NAME) .withUsername(username) .withPassword(password); postgres.start(); } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/RampUpDown.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.pool.TestElf.getPool; import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; import static org.junit.Assert.assertSame; import java.sql.Connection; import java.sql.SQLException; import org.junit.Assert; import org.junit.Test; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; public class RampUpDown { @Test public void rampUpDownTest() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(5); config.setMaximumPoolSize(60); config.setInitializationFailTimeout(0); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "250"); try (HikariDataSource ds = new HikariDataSource(config)) { ds.setIdleTimeout(1000); HikariPool pool = getPool(ds); // wait two housekeeping periods so we don't fail if this part of test runs too quickly quietlySleep(500); Assert.assertSame("Total connections not as expected", 5, pool.getTotalConnections()); Connection[] connections = new Connection[ds.getMaximumPoolSize()]; for (int i = 0; i < connections.length; i++) { connections[i] = ds.getConnection(); } assertSame("Total connections not as expected", 60, pool.getTotalConnections()); for (Connection connection : connections) { connection.close(); } quietlySleep(500); assertSame("Total connections not as expected", 5, pool.getTotalConnections()); } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/RequestBoundariesTest.java ================================================ package com.zaxxer.hikari.pool; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.mocks.StubConnection; import org.junit.Assert; import org.junit.Test; import java.sql.Connection; import static com.zaxxer.hikari.pool.TestElf.getPool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; public class RequestBoundariesTest { private static final HikariConfig config; static { config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(10); config.setInitializationFailTimeout(Long.MAX_VALUE); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); } private HikariPool getHikariPool(boolean enableRequestBoundaries) { System.setProperty("com.zaxxer.hikari.enableRequestBoundaries", String.valueOf(enableRequestBoundaries)); HikariDataSource ds = new HikariDataSource(config); HikariPool pool = getPool(ds); return pool; } @Test public void requestBoundaryEnabledTest() throws Exception { HikariPool pool = getHikariPool(true); Connection conn = pool.getConnection(); StubConnection stubConnection = conn.unwrap(StubConnection.class); Assert.assertTrue("Begin request called", stubConnection.beginRequestCalled); Assert.assertFalse("End request called", stubConnection.endRequestCalled); conn.close(); Assert.assertTrue("Begin request called", stubConnection.beginRequestCalled); Assert.assertTrue("End request called", stubConnection.endRequestCalled); } @Test public void requestBoundaryDisabledTest() throws Exception { HikariPool pool = getHikariPool(false); Connection conn = pool.getConnection(); StubConnection stubConnection = conn.unwrap(StubConnection.class); Assert.assertFalse("Begin request called", stubConnection.beginRequestCalled); Assert.assertFalse("End request called", stubConnection.endRequestCalled); conn.close(); Assert.assertFalse("Begin request called", stubConnection.beginRequestCalled); Assert.assertFalse("End request called", stubConnection.endRequestCalled); } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/ShutdownTest.java ================================================ /* * Copyright (C) 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.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.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import org.apache.logging.log4j.Level; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.mocks.StubConnection; import com.zaxxer.hikari.util.UtilityElf; /** * @author Brett Wooldridge */ public class ShutdownTest { @Before public void beforeTest() { setSlf4jLogLevel(PoolBase.class, Level.DEBUG); setSlf4jLogLevel(HikariPool.class, Level.DEBUG); StubConnection.count.set(0); } @After public void afterTest() { setSlf4jLogLevel(PoolBase.class, Level.WARN); setSlf4jLogLevel(HikariPool.class, Level.WARN); StubConnection.slowCreate = false; } @Test public void testShutdown1() throws SQLException { Assert.assertSame("StubConnection count not as expected", 0, StubConnection.count.get()); StubConnection.slowCreate = true; HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(10); config.setInitializationFailTimeout(Long.MAX_VALUE); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { HikariPool pool = getPool(ds); Thread[] threads = new Thread[10]; for (int i = 0; i < 10; i++) { threads[i] = new Thread() { @Override public void run() { try { if (ds.getConnection() != null) { quietlySleep(SECONDS.toMillis(1)); } } catch (SQLException e) { } } }; threads[i].setDaemon(true); } for (int i = 0; i < 10; i++) { threads[i].start(); } quietlySleep(1800L); assertTrue("Total connection count not as expected, ", pool.getTotalConnections() > 0); ds.close(); assertSame("Active connection count not as expected, ", 0, pool.getActiveConnections()); assertSame("Idle connection count not as expected, ", 0, pool.getIdleConnections()); assertSame("Total connection count not as expected, ", 0, pool.getTotalConnections()); assertTrue(ds.isClosed()); } } @Test public void testShutdown2() throws SQLException { assertSame("StubConnection count not as expected", 0, StubConnection.count.get()); StubConnection.slowCreate = true; HikariConfig config = newHikariConfig(); config.setMinimumIdle(10); config.setMaximumPoolSize(10); config.setInitializationFailTimeout(0); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { HikariPool pool = getPool(ds); quietlySleep(1200L); assertTrue("Total connection count not as expected, ", pool.getTotalConnections() > 0); ds.close(); assertSame("Active connection count not as expected, ", 0, pool.getActiveConnections()); assertSame("Idle connection count not as expected, ", 0, pool.getIdleConnections()); assertSame("Total connection count not as expected, ", 0, pool.getTotalConnections()); assertTrue(ds.toString().startsWith("HikariDataSource (") && ds.toString().endsWith(")")); } } @Test public void testShutdown3() throws SQLException { assertSame("StubConnection count not as expected", 0, StubConnection.count.get()); StubConnection.slowCreate = false; HikariConfig config = newHikariConfig(); config.setMinimumIdle(5); config.setMaximumPoolSize(5); config.setInitializationFailTimeout(0); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { HikariPool pool = getPool(ds); quietlySleep(1200L); assertTrue("Total connection count not as expected, ", pool.getTotalConnections() == 5); ds.close(); assertSame("Active connection count not as expected, ", 0, pool.getActiveConnections()); assertSame("Idle connection count not as expected, ", 0, pool.getIdleConnections()); assertSame("Total connection count not as expected, ", 0, pool.getTotalConnections()); } } @Test public void testShutdown4() throws SQLException { StubConnection.slowCreate = true; HikariConfig config = newHikariConfig(); config.setMinimumIdle(10); config.setMaximumPoolSize(10); config.setInitializationFailTimeout(Long.MAX_VALUE); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { quietlySleep(500L); ds.close(); long startTime = currentTime(); while (elapsedMillis(startTime) < SECONDS.toMillis(5) && threadCount() > 0) { quietlySleep(250); } assertSame("Unreleased connections after shutdown", 0, getPool(ds).getTotalConnections()); } } @Test public void testShutdown5() throws SQLException { Assert.assertSame("StubConnection count not as expected", 0, StubConnection.count.get()); HikariConfig config = newHikariConfig(); config.setMinimumIdle(5); config.setMaximumPoolSize(5); config.setInitializationFailTimeout(0); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { HikariPool pool = getPool(ds); Connection[] connections = new Connection[5]; for (int i = 0; i < 5; i++) { connections[i] = ds.getConnection(); } Assert.assertTrue("Total connection count not as expected, ", pool.getTotalConnections() == 5); ds.close(); Assert.assertSame("Active connection count not as expected, ", 0, pool.getActiveConnections()); Assert.assertSame("Idle connection count not as expected, ", 0, pool.getIdleConnections()); Assert.assertSame("Total connection count not as expected, ", 0, pool.getTotalConnections()); } } @Test public void testAfterShutdown() throws Exception { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(5); config.setInitializationFailTimeout(0); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { ds.close(); try { ds.getConnection(); } catch (SQLException e) { Assert.assertTrue(e.getMessage().contains("has been closed.")); } } } @Test public void testShutdownDuringInit() throws Exception { final HikariConfig config = newHikariConfig(); config.setMinimumIdle(5); config.setMaximumPoolSize(5); config.setConnectionTimeout(1000); config.setValidationTimeout(1000); config.setInitializationFailTimeout(0); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { StubConnection.slowCreate = true; UtilityElf.quietlySleep(3000L); } } @Test public void testThreadedShutdown() throws Exception { final HikariConfig config = newHikariConfig(); config.setMinimumIdle(5); config.setMaximumPoolSize(5); config.setConnectionTimeout(1000); config.setValidationTimeout(1000); config.setInitializationFailTimeout(0); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); for (int i = 0; i < 4; i++) { try (final HikariDataSource ds = new HikariDataSource(config)) { Thread t = new Thread() { @Override public void run() { try (Connection connection = ds.getConnection()) { for (int i = 0; i < 10; i++) { Connection connection2 = null; try { connection2 = ds.getConnection(); PreparedStatement stmt = connection2.prepareStatement("SOMETHING"); UtilityElf.quietlySleep(20); stmt.getMaxFieldSize(); } catch (SQLException e) { try { if (connection2 != null) { connection2.close(); } } catch (SQLException e2) { if (e2.getMessage().contains("shutdown") || e2.getMessage().contains("evicted")) { break; } } } } } catch (Exception e) { Assert.fail(e.getMessage()); } finally { ds.close(); } } }; t.start(); Thread t2 = new Thread() { @Override public void run() { UtilityElf.quietlySleep(100); try { ds.close(); } catch (IllegalStateException e) { Assert.fail(e.getMessage()); } } }; t2.start(); t.join(); t2.join(); ds.close(); } } } private int threadCount() { Thread[] threads = new Thread[Thread.activeCount() * 2]; Thread.enumerate(threads); int count = 0; for (Thread thread : threads) { count += (thread != null && thread.getName().startsWith("Hikari")) ? 1 : 0; } return count; } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/StatementTest.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.pool.TestElf.getPool; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; public class StatementTest { private HikariDataSource ds; @Before public void setup() { HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(2); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); ds = new HikariDataSource(config); } @After public void teardown() { ds.close(); } @Test public void testStatementClose() throws SQLException { ds.getConnection().close(); HikariPool pool = getPool(ds); assertTrue("Total connections not as expected", pool.getTotalConnections() >= 1); assertTrue("Idle connections not as expected", pool.getIdleConnections() >= 1); try (Connection connection = ds.getConnection()) { assertNotNull(connection); assertTrue("Total connections not as expected", pool.getTotalConnections() >= 1); assertTrue("Idle connections not as expected", pool.getIdleConnections() >= 0); Statement statement = connection.createStatement(); assertNotNull(statement); connection.close(); assertTrue(statement.isClosed()); } } @Test public void testAutoStatementClose() throws SQLException { try (Connection connection = ds.getConnection()) { assertNotNull(connection); Statement statement1 = connection.createStatement(); assertNotNull(statement1); Statement statement2 = connection.createStatement(); assertNotNull(statement2); connection.close(); assertTrue(statement1.isClosed()); assertTrue(statement2.isClosed()); } } @Test public void testStatementResultSetProxyClose() throws SQLException { try (Connection connection = ds.getConnection()) { assertNotNull(connection); Statement statement1 = connection.createStatement(); assertNotNull(statement1); Statement statement2 = connection.createStatement(); assertNotNull(statement2); statement1.getResultSet().getStatement().close(); statement2.getGeneratedKeys().getStatement().close(); assertTrue(statement1.isClosed()); assertTrue(statement2.isClosed()); } } @Test public void testDoubleStatementClose() throws SQLException { try (Connection connection = ds.getConnection(); Statement statement1 = connection.createStatement()) { statement1.close(); statement1.close(); } } @Test public void testOutOfOrderStatementClose() throws SQLException { try (Connection connection = ds.getConnection(); Statement statement1 = connection.createStatement(); Statement statement2 = connection.createStatement()) { statement1.close(); statement2.close(); } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/TestConcurrentBag.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.getPool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.pool.TestElf.setSlf4jTargetStream; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.concurrent.CompletableFuture; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.util.ConcurrentBag; /** * * @author Brett Wooldridge */ public class TestConcurrentBag { private static HikariDataSource ds; private static HikariPool pool; @BeforeClass public static void setup() { HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(2); config.setInitializationFailTimeout(0); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); ds = new HikariDataSource(config); pool = getPool(ds); } @AfterClass public static void teardown() { ds.close(); } @Test public void testConcurrentBag() throws Exception { try (ConcurrentBag bag = new ConcurrentBag<>(x -> CompletableFuture.completedFuture(Boolean.TRUE))) { assertEquals(0, bag.values(8).size()); PoolEntry reserved = pool.newPoolEntry(false); bag.add(reserved); bag.reserve(reserved); // reserved PoolEntry inuse = pool.newPoolEntry(false); bag.add(inuse); bag.borrow(2, MILLISECONDS); // in use PoolEntry notinuse = pool.newPoolEntry(false); bag.add(notinuse); // not in use bag.dumpState(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos, true); setSlf4jTargetStream(ConcurrentBag.class, ps); bag.requite(reserved); bag.remove(notinuse); assertTrue(new String(baos.toByteArray()).contains("not borrowed or reserved")); bag.unreserve(notinuse); assertTrue(new String(baos.toByteArray()).contains("was not reserved")); bag.remove(inuse); bag.remove(inuse); assertTrue(new String(baos.toByteArray()).contains("not borrowed or reserved")); bag.close(); try { PoolEntry bagEntry = pool.newPoolEntry(false); bag.add(bagEntry); assertNotEquals(bagEntry, bag.borrow(100, MILLISECONDS)); } catch (IllegalStateException e) { assertTrue(new String(baos.toByteArray()).contains("ignoring add()")); } assertNotNull(notinuse.toString()); } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/TestConnectionCloseBlocking.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; 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.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; import java.sql.Connection; import java.sql.SQLException; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.mocks.MockDataSource; /** * Test for cases when db network connectivity goes down and close is called on existing connections. By default Hikari * blocks longer than getMaximumTimeout (it can hang for a lot of time depending on driver timeout settings). Closing * async the connections fixes this issue. * */ public class TestConnectionCloseBlocking { private static volatile boolean shouldFail = false; // @Test public void testConnectionCloseBlocking() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(1); config.setConnectionTimeout(1500); config.setDataSource(new CustomMockDataSource()); long start = currentTime(); try (HikariDataSource ds = new HikariDataSource(config); Connection connection = ds.getConnection()) { connection.close(); // Hikari only checks for validity for connections with lastAccess > 1000 ms so we sleep for 1001 ms to force // Hikari to do a connection validation which will fail and will trigger the connection to be closed quietlySleep(1100L); shouldFail = true; // on physical connection close we sleep 2 seconds try (Connection connection2 = ds.getConnection()) { assertTrue("Waited longer than timeout", (elapsedMillis(start) < config.getConnectionTimeout())); } } catch (SQLException e) { assertTrue("getConnection failed because close connection took longer than timeout", (elapsedMillis(start) < config.getConnectionTimeout())); } } private static class CustomMockDataSource extends MockDataSource { @Override public Connection getConnection() throws SQLException { Connection mockConnection = super.getConnection(); when(mockConnection.isValid(anyInt())).thenReturn(!shouldFail); doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { if (shouldFail) { SECONDS.sleep(2); } return null; } }).when(mockConnection).close(); return mockConnection; } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.getPool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.pool.TestElf.setSlf4jLogLevel; import static com.zaxxer.hikari.pool.TestElf.setSlf4jTargetStream; import static com.zaxxer.hikari.util.ClockSource.currentTime; import static com.zaxxer.hikari.util.ClockSource.elapsedMillis; import static java.lang.Thread.sleep; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.sql.Connection; import java.sql.SQLException; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.Level; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.mocks.StubConnection; import com.zaxxer.hikari.mocks.StubDataSource; public class TestConnectionTimeoutRetry { @Test public void testConnectionRetries() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(1); config.setConnectionTimeout(2800); config.setValidationTimeout(2800); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { StubDataSource stubDataSource = ds.unwrap(StubDataSource.class); stubDataSource.setThrowException(new SQLException("Connection refused")); long start = currentTime(); try (Connection connection = ds.getConnection()) { connection.close(); fail("Should not have been able to get a connection."); } catch (SQLException e) { long elapsed = elapsedMillis(start); long timeout = config.getConnectionTimeout(); assertTrue("Didn't wait long enough for timeout", (elapsed >= timeout)); } } } @Test public void testConnectionRetries2() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(1); config.setConnectionTimeout(2800); config.setValidationTimeout(2800); config.setInitializationFailTimeout(0); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { final StubDataSource stubDataSource = ds.unwrap(StubDataSource.class); stubDataSource.setThrowException(new SQLException("Connection refused")); ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); scheduler.schedule(new Runnable() { @Override public void run() { stubDataSource.setThrowException(null); } }, 300, TimeUnit.MILLISECONDS); long start = currentTime(); try { try (Connection connection = ds.getConnection()) { // close immediately } long elapsed = elapsedMillis(start); assertTrue("Connection returned too quickly, something is wrong.", elapsed > 250); assertTrue("Waited too long to get a connection.", elapsed < config.getConnectionTimeout()); } catch (SQLException e) { fail("Should not have timed out: " + e.getMessage()); } finally { scheduler.shutdownNow(); } } } @Test public void testConnectionRetries3() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(2); config.setConnectionTimeout(2800); config.setValidationTimeout(2800); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { final Connection connection1 = ds.getConnection(); final Connection connection2 = ds.getConnection(); assertNotNull(connection1); assertNotNull(connection2); ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); scheduler.schedule(new Runnable() { @Override public void run() { try { connection1.close(); } catch (Exception e) { e.printStackTrace(System.err); } } }, 800, MILLISECONDS); long start = currentTime(); try { try (Connection connection3 = ds.getConnection()) { // close immediately } long elapsed = elapsedMillis(start); assertTrue("Waited too long to get a connection.", (elapsed >= 700) && (elapsed < 950)); } catch (SQLException e) { fail("Should not have timed out."); } finally { scheduler.shutdownNow(); } } } @Test public void testConnectionRetries5() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(2); config.setConnectionTimeout(1000); config.setValidationTimeout(1000); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { final Connection connection1 = ds.getConnection(); long start = currentTime(); ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); scheduler.schedule(new Runnable() { @Override public void run() { try { connection1.close(); } catch (Exception e) { e.printStackTrace(System.err); } } }, 250, MILLISECONDS); StubDataSource stubDataSource = ds.unwrap(StubDataSource.class); stubDataSource.setThrowException(new SQLException("Connection refused")); try { try (Connection connection2 = ds.getConnection()) { // close immediately } long elapsed = elapsedMillis(start); assertTrue("Waited too long to get a connection.", (elapsed >= 250) && (elapsed < config.getConnectionTimeout())); } catch (SQLException e) { fail("Should not have timed out."); } finally { scheduler.shutdownNow(); } } } @Test public void testConnectionIdleFill() throws Exception { StubConnection.slowCreate = false; HikariConfig config = newHikariConfig(); config.setMinimumIdle(5); config.setMaximumPoolSize(10); config.setConnectionTimeout(2000); config.setValidationTimeout(2000); config.setConnectionTestQuery("VALUES 2"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "400"); ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos, true); setSlf4jTargetStream(HikariPool.class, ps); try (HikariDataSource ds = new HikariDataSource(config)) { setSlf4jLogLevel(HikariPool.class, Level.DEBUG); HikariPool pool = getPool(ds); try ( Connection connection1 = ds.getConnection(); Connection connection2 = ds.getConnection(); Connection connection3 = ds.getConnection(); Connection connection4 = ds.getConnection(); 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()); } assertSame("Total connections not as expected", 10, pool.getTotalConnections()); assertSame("Idle connections not as expected", 10, pool.getIdleConnections()); } } @Before public void before() { setSlf4jLogLevel(HikariPool.class, Level.INFO); } @After public void after() { System.getProperties().remove("com.zaxxer.hikari.housekeeping.periodMs"); setSlf4jLogLevel(HikariPool.class, Level.INFO); } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/TestConnections.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariPoolMXBean; import com.zaxxer.hikari.SQLExceptionOverride; import com.zaxxer.hikari.mocks.StubConnection; import com.zaxxer.hikari.mocks.StubDataSource; import com.zaxxer.hikari.mocks.StubStatement; import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException; import org.apache.logging.log4j.Level; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.sql.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import static com.zaxxer.hikari.pool.TestElf.*; import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.*; /** * @author Brett Wooldridge */ @SuppressWarnings({"SqlDialectInspection", "SqlNoDataSourceInspection"}) public class TestConnections { @Before public void before() { setSlf4jTargetStream(HikariPool.class, System.err); setSlf4jLogLevel(HikariPool.class, Level.DEBUG); setSlf4jLogLevel(PoolBase.class, Level.DEBUG); } @After public void after() { System.getProperties().remove("com.zaxxer.hikari.housekeeping.periodMs"); setSlf4jLogLevel(HikariPool.class, Level.WARN); setSlf4jLogLevel(PoolBase.class, Level.WARN); } @Test public void testCreate() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(1); config.setConnectionTestQuery("VALUES 1"); config.setConnectionInitSql("SELECT 1"); config.setReadOnly(true); config.setConnectionTimeout(2500); config.setLeakDetectionThreshold(TimeUnit.SECONDS.toMillis(30)); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { ds.setLoginTimeout(10); assertSame(10, ds.getLoginTimeout()); HikariPool pool = getPool(ds); ds.getConnection().close(); assertSame("Total connections not as expected", 1, pool.getTotalConnections()); assertSame("Idle connections not as expected", 1, pool.getIdleConnections()); try (Connection connection = ds.getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM device WHERE device_id=?")) { assertNotNull(connection); assertNotNull(statement); assertSame("Total connections not as expected", 1, pool.getTotalConnections()); assertSame("Idle connections not as expected", 0, pool.getIdleConnections()); statement.setInt(1, 0); try (ResultSet resultSet = statement.executeQuery()) { assertNotNull(resultSet); assertFalse(resultSet.next()); } } assertSame("Total connections not as expected", 1, pool.getTotalConnections()); assertSame("Idle connections not as expected", 1, pool.getIdleConnections()); } } @Test public void testMaxLifetime() throws Exception { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(1); config.setConnectionTimeout(2500); config.setConnectionTestQuery("VALUES 1"); config.setInitializationFailTimeout(Long.MAX_VALUE); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "100"); setConfigUnitTest(true); try (HikariDataSource ds = new HikariDataSource(config)) { System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs"); getUnsealedConfig(ds).setMaxLifetime(700); HikariPool pool = getPool(ds); assertSame("Total connections not as expected", 0, pool.getTotalConnections()); assertSame("Idle connections not as expected", 0, pool.getIdleConnections()); Connection unwrap; Connection unwrap2; try (Connection connection = ds.getConnection()) { unwrap = connection.unwrap(Connection.class); assertNotNull(connection); assertSame("Second total connections not as expected", 1, pool.getTotalConnections()); assertSame("Second idle connections not as expected", 0, pool.getIdleConnections()); } assertSame("Idle connections not as expected", 1, pool.getIdleConnections()); try (Connection connection = ds.getConnection()) { unwrap2 = connection.unwrap(Connection.class); assertSame(unwrap, unwrap2); assertSame("Second total connections not as expected", 1, pool.getTotalConnections()); assertSame("Second idle connections not as expected", 0, pool.getIdleConnections()); } quietlySleep(TimeUnit.SECONDS.toMillis(2)); try (Connection connection = ds.getConnection()) { unwrap2 = connection.unwrap(Connection.class); assertNotSame("Expected a different connection", unwrap, unwrap2); } assertSame("Post total connections not as expected", 1, pool.getTotalConnections()); assertSame("Post idle connections not as expected", 1, pool.getIdleConnections()); } finally { setConfigUnitTest(false); } } @Test public void testMaxLifetime2() throws Exception { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(1); config.setConnectionTimeout(2500); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "100"); setConfigUnitTest(true); try (HikariDataSource ds = new HikariDataSource(config)) { getUnsealedConfig(ds).setMaxLifetime(700); HikariPool pool = getPool(ds); assertSame("Total connections not as expected", 0, pool.getTotalConnections()); assertSame("Idle connections not as expected", 0, pool.getIdleConnections()); Connection unwrap; Connection unwrap2; try (Connection connection = ds.getConnection()) { unwrap = connection.unwrap(Connection.class); assertNotNull(connection); assertSame("Second total connections not as expected", 1, pool.getTotalConnections()); assertSame("Second idle connections not as expected", 0, pool.getIdleConnections()); } assertSame("Idle connections not as expected", 1, pool.getIdleConnections()); try (Connection connection = ds.getConnection()) { unwrap2 = connection.unwrap(Connection.class); assertSame(unwrap, unwrap2); assertSame("Second total connections not as expected", 1, pool.getTotalConnections()); assertSame("Second idle connections not as expected", 0, pool.getIdleConnections()); } quietlySleep(800); try (Connection connection = ds.getConnection()) { unwrap2 = connection.unwrap(Connection.class); assertNotSame("Expected a different connection", unwrap, unwrap2); } assertSame("Post total connections not as expected", 1, pool.getTotalConnections()); assertSame("Post idle connections not as expected", 1, pool.getIdleConnections()); } finally { setConfigUnitTest(false); } } @Test public void testKeepalive() throws Exception{ HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(1); config.setConnectionTimeout(2500); config.setConnectionTestQuery("VALUES 1"); StubDataSource sds = new StubDataSource(); sds.setWaitTimeout(700); config.setDataSource(sds); System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "100"); setConfigUnitTest(true); try (HikariDataSource ds = new HikariDataSource(config)) { getUnsealedConfig(ds).setKeepaliveTime(500); HikariPool pool = getPool(ds); Connection conn = pool.getConnection(); Connection unwrap = conn.unwrap(Connection.class); //recycle, change IN_USE state conn.close(); assertFalse("Connection should be open", unwrap.isClosed()); quietlySleep(1200); assertFalse("Connection should be open", unwrap.isClosed()); } finally { setConfigUnitTest(false); } } @Test public void testKeepalive2() throws Exception{ HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(1); config.setConnectionTimeout(2500); config.setConnectionTestQuery("VALUES 1"); StubDataSource sds = new StubDataSource(); sds.setWaitTimeout(500); config.setDataSource(sds); System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "100"); setConfigUnitTest(true); try (HikariDataSource ds = new HikariDataSource(config)) { getUnsealedConfig(ds).setKeepaliveTime(700); HikariPool pool = getPool(ds); Connection conn = pool.getConnection(); Connection unwrap = conn.unwrap(Connection.class); //recycle, change IN_USE state conn.close(); assertFalse("Connection should be open", unwrap.isClosed()); quietlySleep(1200); assertTrue("Connection should have closed:" + unwrap, unwrap.isClosed()); Connection conn2 = pool.getConnection(); Connection unwrap2 = conn2.unwrap(Connection.class); assertNotSame("Expected a different connection", unwrap, unwrap2); assertFalse("Connection should be open", unwrap2.isClosed()); conn2.close(); } finally { setConfigUnitTest(false); } } @Test public void testDoubleClose() throws Exception { HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(1); config.setConnectionTimeout(2500); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config); Connection connection = ds.getConnection()) { connection.close(); // should no-op connection.abort(null); assertTrue("Connection should have closed", connection.isClosed()); assertFalse("Connection should have closed", connection.isValid(5)); assertTrue("Expected to contain ClosedConnection, but was " + connection, connection.toString().contains("ClosedConnection")); } } @Test public void testCloseMarkEvicted() throws Exception { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(5); config.setConnectionTimeout(2500); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { ProxyConnection connection = (ProxyConnection) ds.getConnection(); HikariPool pool = getPool(ds); assertEquals(1, pool.getTotalConnections()); connection.getPoolEntry().markEvicted(); connection.close(); assertEquals("Connection should have been evicted after close", 0, pool.getTotalConnections()); } } @Test public void testEviction() throws Exception { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(5); config.setConnectionTimeout(2500); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { Connection connection = ds.getConnection(); HikariPool pool = getPool(ds); assertEquals(1, pool.getTotalConnections()); ds.evictConnection(connection); assertEquals(0, pool.getTotalConnections()); } } @Test public void testEviction2() throws SQLException { HikariConfig config = newHikariConfig(); config.setMaximumPoolSize(5); config.setConnectionTimeout(2500); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); config.setExceptionOverrideClassName(OverrideHandler.class.getName()); try (HikariDataSource ds = new HikariDataSource(config)) { HikariPool pool = getPool(ds); while (pool.getTotalConnections() < 5) { quietlySleep(100L); } try (Connection connection = ds.getConnection()) { assertNotNull(connection); PreparedStatement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?"); assertNotNull(statement); ResultSet resultSet = statement.executeQuery(); assertNotNull(resultSet); try { statement.getMaxFieldSize(); } catch (Exception e) { assertSame(SQLException.class, e.getClass()); } } assertEquals("Total connections not as expected", 5, pool.getTotalConnections()); assertEquals("Idle connections not as expected", 5, pool.getIdleConnections()); } } @Test public void testEviction3() throws SQLException { HikariConfig config = newHikariConfig(); config.setMaximumPoolSize(5); config.setConnectionTimeout(2500); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { HikariPool pool = getPool(ds); while (pool.getTotalConnections() < 5) { quietlySleep(100L); } try (Connection connection = ds.getConnection()) { assertNotNull(connection); PreparedStatement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?"); assertNotNull(statement); ResultSet resultSet = statement.executeQuery(); assertNotNull(resultSet); try { statement.getMaxFieldSize(); } catch (Exception e) { assertSame(SQLException.class, e.getClass()); } } assertEquals("Total connections not as expected", 4, pool.getTotalConnections()); assertEquals("Idle connections not as expected", 4, pool.getIdleConnections()); } } @Test public void testEvictAllRefill() throws Exception { HikariConfig config = newHikariConfig(); config.setMinimumIdle(5); config.setMaximumPoolSize(10); config.setConnectionTimeout(2500); 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)) { HikariPoolMXBean poolMXBean = ds.getHikariPoolMXBean(); while (poolMXBean.getIdleConnections() < 5) { // wait until the pool fills quietlySleep(100); } // Get and evict all the idle connections for (int i = 0; i < 5; i++) { final Connection conn = ds.getConnection(); ds.evictConnection(conn); } assertTrue("Expected idle connections to be less than idle", poolMXBean.getIdleConnections() < 5); // Wait a bit quietlySleep(SECONDS.toMillis(2)); int count = 0; while (poolMXBean.getIdleConnections() < 5 && count++ < 20) { quietlySleep(100); } // Assert that the pool as returned to 5 connections assertEquals("After eviction, refill did not reach expected 5 connections.", 5, poolMXBean.getIdleConnections()); } finally { System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs"); } } @Test public void testBackfill() throws Exception { HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(4); config.setConnectionTimeout(1000); config.setInitializationFailTimeout(Long.MAX_VALUE); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); StubConnection.slowCreate = true; try (HikariDataSource ds = new HikariDataSource(config)) { HikariPool pool = getPool(ds); quietlySleep(1250); assertSame("Total connections not as expected", 1, pool.getTotalConnections()); assertSame("Idle connections not as expected", 1, pool.getIdleConnections()); // This will take the pool down to zero try (Connection connection = ds.getConnection()) { assertNotNull(connection); assertSame("Total connections not as expected", 1, pool.getTotalConnections()); assertSame("Idle connections not as expected", 0, pool.getIdleConnections()); PreparedStatement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?"); assertNotNull(statement); ResultSet resultSet = statement.executeQuery(); assertNotNull(resultSet); try { statement.getMaxFieldSize(); fail(); } catch (Exception e) { assertSame(SQLException.class, e.getClass()); } pool.logPoolState("testBackfill() before close..."); // The connection will be ejected from the pool here } assertSame("Total connections not as expected", 0, pool.getTotalConnections()); pool.logPoolState("testBackfill() after close..."); quietlySleep(1250); assertSame("Total connections not as expected", 1, pool.getTotalConnections()); assertSame("Idle connections not as expected", 1, pool.getIdleConnections()); } finally { StubConnection.slowCreate = false; } } @Test public void testMaximumPoolLimit() throws Exception { HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(4); config.setConnectionTimeout(20000); config.setInitializationFailTimeout(0); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); final AtomicReference ref = new AtomicReference<>(); StubConnection.count.set(0); // reset counter try (final HikariDataSource ds = new HikariDataSource(config)) { final HikariPool pool = getPool(ds); Thread[] threads = new Thread[20]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(() -> { try { pool.logPoolState("Before acquire "); try (Connection ignored = ds.getConnection()) { pool.logPoolState("After acquire "); quietlySleep(500); } } catch (Exception e) { ref.set(e); } }); } for (Thread thread : threads) { thread.start(); } for (Thread thread : threads) { thread.join(); } pool.logPoolState("before check "); assertNull((ref.get() != null ? ref.get().toString() : ""), ref.get()); assertSame("StubConnection count not as expected", 4, StubConnection.count.get()); } } @Test @SuppressWarnings("EmptyTryBlock") public void testOldDriver() throws Exception { HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(1); config.setConnectionTimeout(2500); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); StubConnection.oldDriver = true; StubStatement.oldDriver = true; try (HikariDataSource ds = new HikariDataSource(config)) { quietlySleep(500); try (Connection ignored = ds.getConnection()) { // close } quietlySleep(500); try (Connection ignored = ds.getConnection()) { // close } } finally { StubConnection.oldDriver = false; StubStatement.oldDriver = false; } } @Test public void testSuspendResume() throws Exception { HikariConfig config = newHikariConfig(); config.setMinimumIdle(3); config.setMaximumPoolSize(3); config.setConnectionTimeout(2500); config.setAllowPoolSuspension(true); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (final HikariDataSource ds = new HikariDataSource(config)) { HikariPool pool = getPool(ds); while (pool.getTotalConnections() < 3) { quietlySleep(50); } Thread t = new Thread(() -> { try { ds.getConnection(); ds.getConnection(); } catch (Exception e) { fail(); } }); try (Connection ignored = ds.getConnection()) { assertEquals(2, pool.getIdleConnections()); pool.suspendPool(); t.start(); quietlySleep(500); assertEquals(2, pool.getIdleConnections()); } assertEquals(3, pool.getIdleConnections()); pool.resumePool(); quietlySleep(500); assertEquals(1, pool.getIdleConnections()); } } @Test public void testSuspendResumeWithThrow() throws Exception { HikariConfig config = newHikariConfig(); config.setMinimumIdle(3); config.setMaximumPoolSize(3); config.setConnectionTimeout(2500); config.setAllowPoolSuspension(true); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); System.setProperty("com.zaxxer.hikari.throwIfSuspended", "true"); try (final HikariDataSource ds = new HikariDataSource(config)) { HikariPool pool = getPool(ds); while (pool.getTotalConnections() < 3) { quietlySleep(50); } AtomicReference exception = new AtomicReference<>(); Thread t = new Thread(() -> { try { ds.getConnection(); ds.getConnection(); } catch (Exception e) { exception.set(e); } }); try (Connection ignored = ds.getConnection()) { assertEquals(2, pool.getIdleConnections()); pool.suspendPool(); t.start(); quietlySleep(500); assertEquals(SQLTransientException.class, exception.get().getClass()); assertEquals(2, pool.getIdleConnections()); } assertEquals(3, pool.getIdleConnections()); pool.resumePool(); try (Connection ignored = ds.getConnection()) { assertEquals(2, pool.getIdleConnections()); } } finally { System.getProperties().remove("com.zaxxer.hikari.throwIfSuspended"); } } @Test public void testInitializationFailure1() { StubDataSource stubDataSource = new StubDataSource(); stubDataSource.setThrowException(new SQLException("Connection refused")); try (HikariDataSource ds = newHikariDataSource()) { ds.setMinimumIdle(1); ds.setMaximumPoolSize(1); ds.setConnectionTimeout(2500); ds.setConnectionTestQuery("VALUES 1"); ds.setDataSource(stubDataSource); try (Connection ignored = ds.getConnection()) { fail("Initialization should have failed"); } catch (SQLException e) { // passed } } } @Test public void testInitializationFailure2() throws SQLException { StubDataSource stubDataSource = new StubDataSource(); stubDataSource.setThrowException(new SQLException("Connection refused")); HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setConnectionTestQuery("VALUES 1"); config.setDataSource(stubDataSource); try (HikariDataSource ds = new HikariDataSource(config); Connection ignored = ds.getConnection()) { fail("Initialization should have failed"); } catch (PoolInitializationException e) { // passed } } @Test public void testInvalidConnectionTestQuery() { class BadConnection extends StubConnection { /** {@inheritDoc} */ @Override public Statement createStatement() throws SQLException { throw new SQLException("Simulated exception in createStatement()"); } } StubDataSource stubDataSource = new StubDataSource() { /** {@inheritDoc} */ @Override public Connection getConnection() { return new BadConnection(); } }; HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(2); config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(3)); config.setConnectionTestQuery("VALUES 1"); config.setInitializationFailTimeout(TimeUnit.SECONDS.toMillis(2)); config.setDataSource(stubDataSource); try (HikariDataSource ds = new HikariDataSource(config)) { try (Connection ignored = ds.getConnection()) { fail("getConnection() should have failed"); } catch (SQLException e) { assertSame("Simulated exception in createStatement()", e.getNextException().getMessage()); } } catch (PoolInitializationException e) { assertSame("Simulated exception in createStatement()", e.getCause().getMessage()); } config.setInitializationFailTimeout(0); try (HikariDataSource ignored = new HikariDataSource(config)) { fail("Initialization should have failed"); } catch (PoolInitializationException e) { // passed } } @Test public void testDataSourceRaisesErrorWhileInitializationTestQuery() throws SQLException { StubDataSourceWithErrorSwitch stubDataSource = new StubDataSourceWithErrorSwitch(); stubDataSource.setErrorOnConnection(true); HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setConnectionTestQuery("VALUES 1"); config.setDataSource(stubDataSource); try (HikariDataSource ds = new HikariDataSource(config); Connection ignored = ds.getConnection()) { fail("Initialization should have failed"); } catch (PoolInitializationException e) { // passed } } @Test public void testDataSourceRaisesErrorAfterInitializationTestQuery() { StubDataSourceWithErrorSwitch stubDataSource = new StubDataSourceWithErrorSwitch(); HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(2); config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(3)); config.setConnectionTestQuery("VALUES 1"); config.setInitializationFailTimeout(TimeUnit.SECONDS.toMillis(2)); config.setDataSource(stubDataSource); try (HikariDataSource ds = new HikariDataSource(config)) { // this will make datasource throws Error, which will become uncaught stubDataSource.setErrorOnConnection(true); try (Connection ignored = ds.getConnection()) { fail("SQLException should occur!"); } catch (SQLException e) { // request will get timed-out assertTrue(e.getMessage().contains("request timed out")); } } } @Test public void testPopulationSlowAcquisition() throws InterruptedException, SQLException { HikariConfig config = newHikariConfig(); config.setMaximumPoolSize(20); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "1000"); StubConnection.slowCreate = true; try (HikariDataSource ds = new HikariDataSource(config)) { System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs"); getUnsealedConfig(ds).setIdleTimeout(3000); SECONDS.sleep(2); HikariPool pool = getPool(ds); assertSame("Total connections not as expected", 2, pool.getTotalConnections()); assertSame("Idle connections not as expected", 2, pool.getIdleConnections()); try (Connection connection = ds.getConnection()) { assertNotNull(connection); SECONDS.sleep(20); assertSame("Second total connections not as expected", 20, pool.getTotalConnections()); assertSame("Second idle connections not as expected", 19, pool.getIdleConnections()); } assertSame("Idle connections not as expected", 20, pool.getIdleConnections()); SECONDS.sleep(5); assertSame("Third total connections not as expected", 20, pool.getTotalConnections()); assertSame("Third idle connections not as expected", 20, pool.getIdleConnections()); } finally { StubConnection.slowCreate = false; } } @Test @SuppressWarnings("EmptyTryBlock") public void testMinimumIdleZero() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(5); config.setConnectionTimeout(1000L); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config); Connection ignored = ds.getConnection()) { // passed } catch (SQLTransientConnectionException sqle) { fail("Failed to obtain connection"); } } static class StubDataSourceWithErrorSwitch extends StubDataSource { private boolean errorOnConnection = false; /** {@inheritDoc} */ @Override public Connection getConnection() { if (!errorOnConnection) { return new StubConnection(); } throw new RuntimeException("Bad thing happens on datasource."); } public void setErrorOnConnection(boolean errorOnConnection) { this.errorOnConnection = errorOnConnection; } } public static class OverrideHandler implements SQLExceptionOverride { @java.lang.Override public Override adjudicate(SQLException sqlException) { return (sqlException.getSQLState().equals("08999")) ? Override.DO_NOT_EVICT : Override.CONTINUE_EVICT; } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/TestCredentials.java ================================================ package com.zaxxer.hikari.pool; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariCredentialsProvider; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.util.Credentials; import org.junit.Test; import java.time.Duration; import java.util.concurrent.atomic.AtomicBoolean; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static org.apache.commons.lang3.ThreadUtils.sleepQuietly; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class TestCredentials { @Test public void testCredentialsProvider() { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(1); config.setConnectionTimeout(2500); config.setConnectionTestQuery("VALUES 1"); config.setInitializationFailTimeout(Long.MAX_VALUE); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); config.setCredentialsProvider(new TestCredentialsProvider()); try (HikariDataSource ds = new HikariDataSource(config)) { sleepQuietly(Duration.ofMillis(250L)); // Allow time for the credentials provider to be called assertTrue("CredentialsProvider was not called", ((TestCredentialsProvider) ds.getCredentialsProvider()).called.get()); } catch (Exception e) { fail("Exception occurred: " + e.getMessage()); } } public static class TestCredentialsProvider implements HikariCredentialsProvider { AtomicBoolean called = new AtomicBoolean(); @Override public Credentials getCredentials() { called.set(true); return new Credentials("user", "password"); } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/TestElf.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.util.ConcurrentBag; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.layout.CsvLogEventLayout; import org.apache.logging.slf4j.Log4jLogger; import org.slf4j.LoggerFactory; import java.io.DataInputStream; import java.io.IOException; import java.io.PrintStream; import java.lang.reflect.Field; import java.net.URL; import java.sql.Connection; /** * Utility methods for testing. * * @author Brett Wooldridge */ public final class TestElf { private TestElf() { // default constructor } public static boolean isJava11() { return Integer.parseInt(System.getProperty("java.version").split("\\.")[0]) >= 11; } public static HikariPool getPool(final HikariDataSource ds) { try { Field field = ds.getClass().getDeclaredField("pool"); field.setAccessible(true); return (HikariPool) field.get(ds); } catch (Exception e) { throw new RuntimeException(e); } } static ConcurrentBag getConcurrentBag(final HikariDataSource ds) { try { Field field = HikariPool.class.getDeclaredField("connectionBag"); field.setAccessible(true); return (ConcurrentBag) field.get(getPool(ds)); } catch (Exception e) { throw new RuntimeException(e); } } public static HikariConfig getUnsealedConfig(final HikariDataSource ds) { try { HikariPool pool = getPool(ds); Field configField = PoolBase.class.getDeclaredField("config"); configField.setAccessible(true); HikariConfig config = (HikariConfig) configField.get(pool); Field field = HikariConfig.class.getDeclaredField("sealed"); field.setAccessible(true); field.setBoolean(config, false); return config; } catch (Exception e) { throw new RuntimeException(e); } } static boolean getConnectionCommitDirtyState(final Connection connection) { try { Field field = ProxyConnection.class.getDeclaredField("isCommitStateDirty"); field.setAccessible(true); return field.getBoolean(connection); } catch (Exception e) { throw new RuntimeException(e); } } static void setConfigUnitTest(final boolean unitTest) { try { Field field = HikariConfig.class.getDeclaredField("unitTest"); field.setAccessible(true); field.setBoolean(null, unitTest); } catch (Exception e) { throw new RuntimeException(e); } } static void setSlf4jTargetStream(final Class clazz, final PrintStream stream) { try { Log4jLogger log4Jlogger = (Log4jLogger) LoggerFactory.getLogger(clazz); Field field = clazz.getClassLoader().loadClass("org.apache.logging.slf4j.Log4jLogger").getDeclaredField("logger"); field.setAccessible(true); Logger logger = (Logger) field.get(log4Jlogger); if (logger.getAppenders().containsKey("string")) { Appender appender = logger.getAppenders().get("string"); logger.removeAppender(appender); } logger.addAppender(new StringAppender("string", stream)); } catch (Exception e) { throw new RuntimeException(e); } } static void setSlf4jLogLevel(final Class clazz, final Level logLevel) { try { Log4jLogger log4Jlogger = (Log4jLogger) LoggerFactory.getLogger(clazz); Field field = clazz.getClassLoader().loadClass("org.apache.logging.slf4j.Log4jLogger").getDeclaredField("logger"); field.setAccessible(true); Logger logger = (Logger) field.get(log4Jlogger); logger.setLevel(logLevel); } catch (Exception e) { throw new RuntimeException(e); } } public static HikariConfig newHikariConfig() { final StackTraceElement callerStackTrace = Thread.currentThread().getStackTrace()[2]; String poolName = callerStackTrace.getMethodName(); if ("setup".equals(poolName)) { poolName = callerStackTrace.getClassName(); } final HikariConfig config = new HikariConfig(); config.setPoolName(poolName); return config; } static HikariDataSource newHikariDataSource() { final StackTraceElement callerStackTrace = Thread.currentThread().getStackTrace()[2]; String poolName = callerStackTrace.getMethodName(); if ("setup".equals(poolName)) { poolName = callerStackTrace.getClassName(); } final HikariDataSource ds = new HikariDataSource(); ds.setPoolName(poolName); return ds; } private static class StringAppender extends AbstractAppender { private PrintStream stream; StringAppender(final String name, final PrintStream stream) { super(name, null, CsvLogEventLayout.createDefaultLayout(), true, Property.EMPTY_ARRAY); this.stream = stream; } @Override public void append(final LogEvent event) { stream.println(event.getMessage().getFormattedMessage()); } } public static class FauxWebClassLoader extends ClassLoader { static final byte[] classBytes = new byte[16_000]; @Override public Class loadClass(final String name) throws ClassNotFoundException { if (name.startsWith("java") || name.startsWith("org")) { return super.loadClass(name, true); } final String resourceName = "/" + name.replace('.', '/') + ".class"; final URL resource = this.getClass().getResource(resourceName); try (DataInputStream is = new DataInputStream(resource.openStream())) { int read = 0; while (read < classBytes.length) { final int rc = is.read(classBytes, read, classBytes.length - read); if (rc == -1) { break; } read += rc; } return defineClass(name, classBytes, 0, read); } catch (IOException e) { throw new ClassNotFoundException(name); } } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/TestHibernate.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import java.sql.Connection; import java.util.Properties; import org.hibernate.service.UnknownUnwrapTypeException; import org.junit.Test; import com.zaxxer.hikari.hibernate.HikariConnectionProvider; public class TestHibernate { @Test public void testConnectionProvider() throws Exception { HikariConnectionProvider provider = new HikariConnectionProvider(); Properties props = new Properties(); props.load(getClass().getResourceAsStream("/hibernate.properties")); provider.configure(props); Connection connection = provider.getConnection(); provider.closeConnection(connection); assertNotNull(provider.unwrap(HikariConnectionProvider.class)); assertFalse(provider.supportsAggressiveRelease()); try { provider.unwrap(TestHibernate.class); fail("Expected exception"); } catch (UnknownUnwrapTypeException e) { } provider.stop(); } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/TestIsRunning.java ================================================ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.getPool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.concurrent.TimeUnit; import org.junit.Test; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; /** * Tests for {@link HikariDataSource#isRunning()}. */ public class TestIsRunning { @Test public void testRunningNormally() { try (HikariDataSource ds = new HikariDataSource(basicConfig())) { assertTrue(ds.isRunning()); } } @Test public void testNoPool() { try (HikariDataSource ds = newHikariDataSource()) { assertNull("Pool should not be initialized.", getPool(ds)); assertFalse(ds.isRunning()); } } @Test public void testSuspendAndResume() { try (HikariDataSource ds = new HikariDataSource(basicConfig())) { ds.getHikariPoolMXBean().suspendPool(); assertFalse(ds.isRunning()); ds.getHikariPoolMXBean().resumePool(); assertTrue(ds.isRunning()); } } @Test public void testShutdown() { try (HikariDataSource ds = new HikariDataSource(basicConfig())) { ds.close(); assertFalse(ds.isRunning()); } } private HikariConfig basicConfig() { HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(1); config.setConnectionTestQuery("VALUES 1"); config.setConnectionInitSql("SELECT 1"); config.setReadOnly(true); config.setConnectionTimeout(2500); config.setLeakDetectionThreshold(TimeUnit.SECONDS.toMillis(30)); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); config.setAllowPoolSuspension(true); return config; } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/TestJNDI.java ================================================ /* * Copyright (C) 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariJNDIFactory; import com.zaxxer.hikari.mocks.StubDataSource; import org.junit.Test; import org.osjava.sj.jndi.AbstractContext; import javax.naming.*; import java.sql.Connection; import static com.zaxxer.hikari.pool.TestElf.getUnsealedConfig; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static org.junit.Assert.*; public class TestJNDI { @Test public void testJndiLookup1() throws Exception { HikariJNDIFactory jndi = new HikariJNDIFactory(); Reference ref = new Reference("javax.sql.DataSource"); ref.add(new BogusRef("driverClassName", "com.zaxxer.hikari.mocks.StubDriver")); ref.add(new BogusRef("jdbcUrl", "jdbc:stub")); ref.add(new BogusRef("username", "foo")); ref.add(new BogusRef("password", "foo")); ref.add(new BogusRef("minimumIdle", "0")); ref.add(new BogusRef("maxLifetime", "30000")); ref.add(new BogusRef("maximumPoolSize", "10")); ref.add(new BogusRef("dataSource.loginTimeout", "10")); Context nameCtx = new BogusContext(); try (HikariDataSource ds = (HikariDataSource) jndi.getObjectInstance(ref, null, nameCtx, null)) { assertNotNull(ds); assertEquals("foo", getUnsealedConfig(ds).getUsername()); } } @Test public void testJndiLookup2() throws Exception { HikariJNDIFactory jndi = new HikariJNDIFactory(); Reference ref = new Reference("javax.sql.DataSource"); ref.add(new BogusRef("dataSourceJNDI", "java:comp/env/HikariDS")); ref.add(new BogusRef("driverClassName", "com.zaxxer.hikari.mocks.StubDriver")); ref.add(new BogusRef("jdbcUrl", "jdbc:stub")); ref.add(new BogusRef("username", "foo")); ref.add(new BogusRef("password", "foo")); ref.add(new BogusRef("minimumIdle", "0")); ref.add(new BogusRef("maxLifetime", "30000")); ref.add(new BogusRef("maximumPoolSize", "10")); ref.add(new BogusRef("dataSource.loginTimeout", "10")); Context nameCtx = new BogusContext2(); try (HikariDataSource ds = (HikariDataSource) jndi.getObjectInstance(ref, null, nameCtx, null)) { assertNotNull(ds); assertEquals("foo", getUnsealedConfig(ds).getUsername()); } } @Test public void testJndiLookup3() throws Exception { HikariJNDIFactory jndi = new HikariJNDIFactory(); Reference ref = new Reference("javax.sql.DataSource"); ref.add(new BogusRef("dataSourceJNDI", "java:comp/env/HikariDS")); try { jndi.getObjectInstance(ref, null, null, null); fail(); } catch (RuntimeException e) { assertTrue(e.getMessage().contains("JNDI context does not found")); } } @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); } } @SuppressWarnings("unchecked") private class BogusContext extends AbstractContext { @Override public Context createSubcontext(Name name) { return null; } @Override public Object lookup(String name) { final HikariDataSource ds = new HikariDataSource(); ds.setPoolName("TestJNDI"); return ds; } } @SuppressWarnings("unchecked") private class BogusContext2 extends AbstractContext { @Override public Context createSubcontext(Name name) { return null; } @Override public Object lookup(String name) { return new StubDataSource(); } } private class BogusRef extends RefAddr { private static final long serialVersionUID = 1L; private String content; BogusRef(String type, String content) { super(type); this.content = content; } @Override public Object getContent() { return content; } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/TestJavassistCodegen.java ================================================ package com.zaxxer.hikari.pool; import com.zaxxer.hikari.mocks.StubConnection; import com.zaxxer.hikari.pool.TestElf.FauxWebClassLoader; import com.zaxxer.hikari.util.JavassistProxyFactory; import org.junit.Assert; import org.junit.Test; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.sql.Connection; import java.sql.Statement; import java.util.stream.Stream; public class TestJavassistCodegen { @Test public void testCodegen() throws Exception { String tmp = System.getProperty("java.io.tmpdir"); JavassistProxyFactory.main(tmp); Path base = Paths.get(tmp, "target/classes/com/zaxxer/hikari/pool".split("/")); Assert.assertTrue("", Files.isRegularFile(base.resolve("HikariProxyConnection.class"))); Assert.assertTrue("", Files.isRegularFile(base.resolve("HikariProxyStatement.class"))); Assert.assertTrue("", Files.isRegularFile(base.resolve("HikariProxyCallableStatement.class"))); Assert.assertTrue("", Files.isRegularFile(base.resolve("HikariProxyPreparedStatement.class"))); Assert.assertTrue("", Files.isRegularFile(base.resolve("HikariProxyResultSet.class"))); Assert.assertTrue("", Files.isRegularFile(base.resolve("ProxyFactory.class"))); FauxWebClassLoader fauxClassLoader = new FauxWebClassLoader(); int mod = fauxClassLoader.loadClass("com.zaxxer.hikari.pool.HikariProxyConnection").getModifiers(); Assert.assertTrue("Generated proxy class should be public", Modifier.isPublic(mod)); Assert.assertTrue("Generated proxy class should be final", Modifier.isFinal(mod)); Class proxyFactoryClass = fauxClassLoader.loadClass("com.zaxxer.hikari.pool.ProxyFactory"); Connection connection = new StubConnection(); Class fastListClass = fauxClassLoader.loadClass("com.zaxxer.hikari.util.FastList"); Object fastList = fastListClass.getConstructor(Class.class).newInstance(Statement.class); Object proxyConnection = getMethod(proxyFactoryClass, "getProxyConnection") .invoke(null, null /*poolEntry*/, connection, fastList, null /*leakTask*/, Boolean.FALSE /*isReadOnly*/, Boolean.FALSE /*isAutoCommit*/); Assert.assertNotNull(proxyConnection); Object proxyStatement = getMethod(proxyConnection.getClass(), "createStatement", 0) .invoke(proxyConnection); Assert.assertNotNull(proxyStatement); } private Method getMethod(Class clazz, String methodName, Integer... parameterCount) { return Stream.of(clazz.getDeclaredMethods()) .filter(method -> method.getName().equals(methodName)) .filter(method -> (parameterCount.length == 0 || parameterCount[0] == method.getParameterCount())) .peek(method -> method.setAccessible(true)) .findFirst() .orElseThrow(RuntimeException::new); } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/TestMBean.java ================================================ /* * 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 com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariConfigMXBean; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariPoolMXBean; import com.zaxxer.hikari.mocks.StubDataSource; import com.zaxxer.hikari.util.Credentials; 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 static com.zaxxer.hikari.pool.TestElf.getUnsealedConfig; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class TestMBean { @Test public void testMBeanRegistration() { 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"); new HikariDataSource(config).close(); } @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)) { TimeUnit.SECONDS.sleep(1); getUnsealedConfig(ds).setIdleTimeout(3000); 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 ignored = ds.getConnection()) { assertEquals(1, hikariPoolMXBean.getActiveConnections()); TimeUnit.SECONDS.sleep(1); assertEquals(3, hikariPoolMXBean.getIdleConnections()); assertEquals(4, hikariPoolMXBean.getTotalConnections()); } TimeUnit.SECONDS.sleep(3); assertEquals(0, hikariPoolMXBean.getActiveConnections()); assertEquals(3, hikariPoolMXBean.getIdleConnections()); assertEquals(3, hikariPoolMXBean.getTotalConnections()); } finally { System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs"); } System.setProperty("hikaricp.jmx.register2.0", "true"); try (HikariDataSource ds = new HikariDataSource(config)) { getUnsealedConfig(ds).setIdleTimeout(3000); TimeUnit.SECONDS.sleep(1); MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); ObjectName poolName = new ObjectName("com.zaxxer.hikari:type=Pool,name=testMBeanReporting"); HikariPoolMXBean hikariPoolMXBean = JMX.newMXBeanProxy(mBeanServer, poolName, HikariPoolMXBean.class); assertEquals(0, hikariPoolMXBean.getActiveConnections()); assertEquals(3, hikariPoolMXBean.getIdleConnections()); } finally { System.clearProperty("hikaricp.jmx.register2.0"); } } @Test public void testMBeanChange() { 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"); try (HikariDataSource ds = new HikariDataSource(config)) { HikariConfigMXBean hikariConfigMXBean = ds.getHikariConfigMXBean(); hikariConfigMXBean.setIdleTimeout(3000); assertEquals(3000, ds.getIdleTimeout()); } } @Test public void testMBeanConnectionTimeoutChange() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(2); config.setRegisterMbeans(true); config.setConnectionTimeout(2800); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "250"); try (HikariDataSource ds = new HikariDataSource(config)) { HikariConfigMXBean hikariConfigMXBean = ds.getHikariConfigMXBean(); assertEquals(2800, hikariConfigMXBean.getConnectionTimeout()); final StubDataSource stubDataSource = ds.unwrap(StubDataSource.class); // connection acquisition takes more than 0 ms in a real system stubDataSource.setConnectionAcquisitionTime(1200); hikariConfigMXBean.setConnectionTimeout(1000); quietlySleep(500); try (Connection conn1 = ds.getConnection(); Connection conn2 = ds.getConnection()) { fail("Connection should have timed out."); } catch (SQLException e) { assertEquals(1000, ds.getConnectionTimeout()); } } finally { System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs"); } } @Test public void testMBeanCredentialRotation() { 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"); config.setCredentials(Credentials.of("foo", "bar")); try (HikariDataSource ds = new HikariDataSource(config)) { HikariConfigMXBean hikariConfigMXBean = ds.getHikariConfigMXBean(); hikariConfigMXBean.setCredentials(Credentials.of("newFoo", "newBar")); assertEquals("newFoo", ds.getUsername()); assertEquals("newBar", ds.getPassword()); } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/TestMetrics.java ================================================ /* * 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 com.zaxxer.hikari.HikariDataSource; import org.junit.Test; import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource; /** * Test HikariCP metrics integration. * * @author Brett Wooldridge */ public class TestMetrics { @Test(expected = IllegalArgumentException.class) public void testFakeMetricRegistryThrowsIllegalArgumentException() { try (HikariDataSource ds = newHikariDataSource()) { ds.setMaximumPoolSize(1); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); FakeMetricRegistry metricRegistry = new FakeMetricRegistry(); ds.setMetricRegistry(metricRegistry); } } private static class FakeMetricRegistry {} } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/TestMetricsBase.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import java.sql.Connection; import java.sql.SQLException; import java.util.SortedMap; import java.util.concurrent.TimeUnit; import com.codahale.metrics.Histogram; import com.codahale.metrics.Metric; import com.codahale.metrics.MetricFilter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; import com.codahale.metrics.health.HealthCheck.Result; import com.codahale.metrics.health.HealthCheckRegistry; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.metrics.MetricsTrackerFactory; import com.zaxxer.hikari.metrics.dropwizard.CodahaleMetricsTrackerFactory; import com.zaxxer.hikari.util.UtilityElf; import org.junit.Test; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource; import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; /** * Test HikariCP/CodaHale/Dropwizard 5 metrics integration. * *

* This base test class contains tests common to Codahale metrics testing (pre-5) and Dropwizard 5 metrics testing. * That's the idea behind the registry type parameterization and abstract methods. * Include health checks when implemented for Dropwizard 5. * There's still a bit of duplication between the extending classes. * * @author Brett Wooldridge */ abstract class TestMetricsBase { protected abstract MetricsTrackerFactory metricsTrackerFactory(M metricRegistry); protected abstract M metricRegistry(); @Test public void testSetters3() throws Exception { try (HikariDataSource ds = newHikariDataSource()) { ds.setMaximumPoolSize(1); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); M metricRegistry = metricRegistry(); MetricsTrackerFactory metricsTrackerFactory = metricsTrackerFactory(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"); } catch (IllegalStateException ise) { // pass try { // and never again... (even when calling another method) ds.setMetricRegistry(metricRegistry); fail("Should not have been allowed to set registry after pool started"); } catch (IllegalStateException ise2) { // pass } } } } @Test public void testSetters4() throws Exception { try (HikariDataSource ds = newHikariDataSource()) { ds.setMaximumPoolSize(1); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); M metricRegistry = 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"); } catch (IllegalStateException ise) { // pass } } } @Test public void testSetters5() throws Exception { try (HikariDataSource ds = newHikariDataSource()) { ds.setMaximumPoolSize(1); ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); M metricRegistry = metricRegistry(); MetricsTrackerFactory metricsTrackerFactory = metricsTrackerFactory(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"); } catch (IllegalStateException ise) { // pass } } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/TestPropertySetter.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import java.time.Duration; import java.util.Properties; import java.util.Set; import javax.sql.DataSource; import org.junit.Test; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.util.PropertyElf; public class TestPropertySetter { @Test public void testProperty1() throws Exception { Properties propfile1 = new Properties(); propfile1.load(TestPropertySetter.class.getResourceAsStream("/propfile1.properties")); HikariConfig config = new HikariConfig(propfile1); config.validate(); assertEquals(5, config.getMinimumIdle()); assertEquals("SELECT 1", config.getConnectionTestQuery()); } @Test public void testProperty2() throws Exception { Properties propfile2 = new Properties(); propfile2.load(TestPropertySetter.class.getResourceAsStream("/propfile2.properties")); HikariConfig config = new HikariConfig(propfile2); config.validate(); Class clazz = this.getClass().getClassLoader().loadClass(config.getDataSourceClassName()); DataSource dataSource = (DataSource) clazz.getDeclaredConstructor().newInstance(); PropertyElf.setTargetFromProperties(dataSource, config.getDataSourceProperties()); } @Test public void testObjectProperty() throws Exception { HikariConfig config = newHikariConfig(); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); PrintWriter writer = new PrintWriter(new ByteArrayOutputStream()); config.addDataSourceProperty("logWriter", writer); Class clazz = this.getClass().getClassLoader().loadClass(config.getDataSourceClassName()); DataSource dataSource = (DataSource) clazz.getDeclaredConstructor().newInstance(); PropertyElf.setTargetFromProperties(dataSource, config.getDataSourceProperties()); assertSame(PrintWriter.class, dataSource.getLogWriter().getClass()); } @Test public void testPropertyUpperCase() throws Exception { Properties propfile3 = new Properties(); propfile3.load(TestPropertySetter.class.getResourceAsStream("/propfile3.properties")); HikariConfig config = new HikariConfig(propfile3); config.validate(); Class clazz = this.getClass().getClassLoader().loadClass(config.getDataSourceClassName()); DataSource dataSource = (DataSource) clazz.getDeclaredConstructor().newInstance(); PropertyElf.setTargetFromProperties(dataSource, config.getDataSourceProperties()); } @Test public void testDurationPropertiesSet() throws Exception { Properties durationProperties = new Properties(); durationProperties.load(TestPropertySetter.class.getResourceAsStream("/duration-config.properties")); HikariConfig config = new HikariConfig(durationProperties); config.validate(); assertEquals(Duration.ofMillis(2000), Duration.ofMillis(config.getConnectionTimeout())); assertEquals(Duration.ofSeconds(22), Duration.ofMillis(config.getValidationTimeout())); assertEquals(Duration.ofMinutes(33), Duration.ofMillis(config.getIdleTimeout())); assertEquals(Duration.ofHours(44), Duration.ofMillis(config.getLeakDetectionThreshold())); assertEquals(Duration.ofDays(55), Duration.ofMillis(config.getMaxLifetime())); Class clazz = this.getClass().getClassLoader().loadClass(config.getDataSourceClassName()); DataSource dataSource = (DataSource) clazz.getDeclaredConstructor().newInstance(); PropertyElf.setTargetFromProperties(dataSource, config.getDataSourceProperties()); assertEquals(Duration.ofMinutes(1), Duration.ofMillis(dataSource.getLoginTimeout())); } @Test public void testGetPropertyNames() throws Exception { Set propertyNames = PropertyElf.getPropertyNames(HikariConfig.class); assertTrue(propertyNames.contains("dataSourceClassName")); } @Test public void testSetNonExistantPropertyName() throws Exception { RuntimeException e = assertThrows(RuntimeException.class, () -> { Properties props = new Properties(); props.put("what", "happened"); PropertyElf.setTargetFromProperties(new HikariConfig(), props); }); assertEquals("Property what does not exist on target class com.zaxxer.hikari.HikariConfig", e.getMessage()); } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/TestProxies.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.concurrent.TimeUnit; import org.junit.Test; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.mocks.StubConnection; import com.zaxxer.hikari.mocks.StubStatement; public class TestProxies { @Test public void testProxyCreation() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(1); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { Connection conn = ds.getConnection(); assertNotNull(conn.createStatement(ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE)); assertNotNull(conn.createStatement(ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.HOLD_CURSORS_OVER_COMMIT)); assertNotNull(conn.prepareCall("some sql")); assertNotNull(conn.prepareCall("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE)); assertNotNull(conn.prepareCall("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.HOLD_CURSORS_OVER_COMMIT)); assertNotNull(conn.prepareStatement("some sql", PreparedStatement.NO_GENERATED_KEYS)); assertNotNull(conn.prepareStatement("some sql", new int[3])); assertNotNull(conn.prepareStatement("some sql", new String[3])); assertNotNull(conn.prepareStatement("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE)); assertNotNull(conn.prepareStatement("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.HOLD_CURSORS_OVER_COMMIT)); assertNotNull(conn.toString()); assertTrue(conn.isWrapperFor(Connection.class)); assertTrue(conn.isValid(10)); assertFalse(conn.isClosed()); assertTrue(conn.unwrap(StubConnection.class) instanceof StubConnection); try { conn.unwrap(TestProxies.class); fail(); } catch (SQLException e) { // pass } } } @Test public void testStatementProxy() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(1); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { Connection conn = ds.getConnection(); PreparedStatement stmt = conn.prepareStatement("some sql"); stmt.executeQuery(); stmt.executeQuery("some sql"); assertFalse(stmt.isClosed()); assertNotNull(stmt.getGeneratedKeys()); assertNotNull(stmt.getResultSet()); assertNotNull(stmt.getConnection()); assertTrue(stmt.unwrap(StubStatement.class) instanceof StubStatement); try { stmt.unwrap(TestProxies.class); fail(); } catch (SQLException e) { // pass } } } @Test public void testStatementExceptions() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(1); config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(1)); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { Connection conn = ds.getConnection(); StubConnection stubConnection = conn.unwrap(StubConnection.class); stubConnection.throwException = true; try { conn.createStatement(); fail(); } catch (SQLException e) { // pass } try { conn.createStatement(0, 0); fail(); } catch (SQLException e) { // pass } try { conn.createStatement(0, 0, 0); fail(); } catch (SQLException e) { // pass } try { conn.prepareCall(""); fail(); } catch (SQLException e) { // pass } try { conn.prepareCall("", 0, 0); fail(); } catch (SQLException e) { // pass } try { conn.prepareCall("", 0, 0, 0); fail(); } catch (SQLException e) { // pass } try { conn.prepareStatement(""); fail(); } catch (SQLException e) { // pass } try { conn.prepareStatement("", 0); fail(); } catch (SQLException e) { // pass } try { conn.prepareStatement("", new int[0]); fail(); } catch (SQLException e) { // pass } try { conn.prepareStatement("", new String[0]); fail(); } catch (SQLException e) { // pass } try { conn.prepareStatement("", 0, 0); fail(); } catch (SQLException e) { // pass } try { conn.prepareStatement("", 0, 0, 0); fail(); } catch (SQLException e) { // pass } } } @Test public void testOtherExceptions() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(1); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { try (Connection conn = ds.getConnection()) { StubConnection stubConnection = conn.unwrap(StubConnection.class); stubConnection.throwException = true; try { conn.setTransactionIsolation(Connection.TRANSACTION_NONE); fail(); } catch (SQLException e) { // pass } try { conn.isReadOnly(); fail(); } catch (SQLException e) { // pass } try { conn.setReadOnly(false); fail(); } catch (SQLException e) { // pass } try { conn.setCatalog(""); fail(); } catch (SQLException e) { // pass } try { conn.setAutoCommit(false); fail(); } catch (SQLException e) { // pass } try { conn.clearWarnings(); fail(); } catch (SQLException e) { // pass } try { conn.isValid(0); fail(); } catch (SQLException e) { // pass } try { conn.isWrapperFor(getClass()); fail(); } catch (SQLException e) { // pass } try { conn.unwrap(getClass()); fail(); } catch (SQLException e) { // pass } try { conn.close(); fail(); } catch (SQLException e) { // pass } try { assertFalse(conn.isValid(0)); } catch (SQLException e) { fail(); } } } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/TestSaturatedPool830.java ================================================ /* * 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.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.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.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 TestSaturatedPool830 { private static final Logger LOGGER = LoggerFactory.getLogger(TestSaturatedPool830.class); private static final int MAX_POOL_SIZE = 10; @Test public void saturatedPoolTest() throws Exception { HikariConfig config = newHikariConfig(); config.setMinimumIdle(5); config.setMaximumPoolSize(MAX_POOL_SIZE); config.setInitializationFailTimeout(Long.MAX_VALUE); config.setConnectionTimeout(1000); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); StubConnection.slowCreate = true; StubStatement.setSimulatedQueryTime(1000); setSlf4jLogLevel(HikariPool.class, Level.DEBUG); System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "5000"); final long start = currentTime(); try (final HikariDataSource ds = new HikariDataSource(config)) { LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 50 /*core*/, 50 /*max*/, 2 /*keepalive*/, SECONDS, queue, new ThreadPoolExecutor.CallerRunsPolicy()); threadPool.allowCoreThreadTimeOut(true); AtomicInteger windowIndex = new AtomicInteger(); boolean[] failureWindow = new boolean[100]; Arrays.fill(failureWindow, true); // Initial saturation for (int i = 0; i < 50; i++) { threadPool.execute(() -> { try (Connection conn = ds.getConnection(); Statement stmt = conn.createStatement()) { stmt.execute("SELECT bogus FROM imaginary"); } catch (SQLException e) { LOGGER.info(e.getMessage()); } }); } long sleep = 80; outer: while (true) { quietlySleep(sleep); if (elapsedMillis(start) > SECONDS.toMillis(12) && sleep < 100) { sleep = 100; LOGGER.warn("Switching to 100ms sleep"); } else if (elapsedMillis(start) > SECONDS.toMillis(6) && sleep < 90) { sleep = 90; LOGGER.warn("Switching to 90ms sleep"); } threadPool.execute(() -> { int ndx = windowIndex.incrementAndGet() % failureWindow.length; try (Connection conn = ds.getConnection(); Statement stmt = conn.createStatement()) { stmt.execute("SELECT bogus FROM imaginary"); failureWindow[ndx] = false; } catch (SQLException e) { LOGGER.info(e.getMessage()); failureWindow[ndx] = true; } }); for (int i = 0; i < failureWindow.length; i++) { if (failureWindow[i]) { if (elapsedMillis(start) % (SECONDS.toMillis(1) - sleep) < sleep) { LOGGER.info("Active threads {}, submissions per second {}, waiting threads {}", threadPool.getActiveCount(), SECONDS.toMillis(1) / sleep, getPool(ds).getThreadsAwaitingConnection()); } continue outer; } } LOGGER.info("Timeouts have subsided."); LOGGER.info("Active threads {}, submissions per second {}, waiting threads {}", threadPool.getActiveCount(), SECONDS.toMillis(1) / sleep, getPool(ds).getThreadsAwaitingConnection()); break; } LOGGER.info("Waiting for completion of {} active tasks.", threadPool.getActiveCount()); while (getPool(ds).getActiveConnections() > 0) { quietlySleep(50); } assertEquals("Rate not in balance at 10req/s", 10L, SECONDS.toMillis(1) / sleep); } finally { StubStatement.setSimulatedQueryTime(0); StubConnection.slowCreate = false; System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs"); setSlf4jLogLevel(HikariPool.class, Level.INFO); } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/TestStates.java ================================================ /* * 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 com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.mocks.StubConnection; import org.junit.Test; import java.sql.Connection; import java.sql.SQLException; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static org.junit.Assert.fail; /** * @author Yanming Zhou */ public class TestStates { @Test public void testGetBeforeSet() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(1); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { Connection conn = ds.getConnection(); StubConnection stub = conn.unwrap(StubConnection.class); stub.throwException = true; try { conn.isReadOnly(); fail(); } catch (SQLException e) { // pass } try { conn.getAutoCommit(); fail(); } catch (SQLException e) { // pass } try { conn.getTransactionIsolation(); fail(); } catch (SQLException e) { // pass } try { conn.getCatalog(); fail(); } catch (SQLException e) { // pass } try { conn.getNetworkTimeout(); fail(); } catch (SQLException e) { // pass } try { conn.getSchema(); fail(); } catch (SQLException e) { // pass } } } @Test public void testGetAfterSet() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(0); config.setMaximumPoolSize(1); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { Connection conn = ds.getConnection(); StubConnection stub = conn.unwrap(StubConnection.class); conn.setReadOnly(true); conn.setAutoCommit(true); conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); conn.setCatalog("catalog"); conn.setNetworkTimeout(null, 10); conn.setSchema("schema"); stub.throwException = true; // should not throw exception even if stub throws exception conn.isReadOnly(); conn.getAutoCommit(); conn.getTransactionIsolation(); conn.getCatalog(); conn.getNetworkTimeout(); conn.getSchema(); } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/TestValidation.java ================================================ /* * Copyright (C) 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.pool.TestElf.setSlf4jTargetStream; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.concurrent.TimeUnit; import org.junit.Test; import com.zaxxer.hikari.HikariConfig; /** * @author Brett Wooldridge */ public class TestValidation { @Test public void validateLoadProperties() { System.setProperty("hikaricp.configurationFile", "/propfile1.properties"); HikariConfig config = newHikariConfig(); System.clearProperty("hikaricp.configurationFile"); assertEquals(5, config.getMinimumIdle()); } @Test public void validateMissingProperties() { try { HikariConfig config = new HikariConfig("missing"); config.validate(); } catch (IllegalArgumentException ise) { assertTrue(ise.getMessage().contains("property file")); } } @Test public void validateMissingDS() { try { HikariConfig config = newHikariConfig(); config.validate(); fail(); } catch (IllegalArgumentException ise) { assertTrue(ise.getMessage().contains("dataSource or dataSourceClassName or jdbcUrl is required.")); } } @Test public void validateMissingUrl() { try { HikariConfig config = newHikariConfig(); config.setDriverClassName("com.zaxxer.hikari.mocks.StubDriver"); config.validate(); fail(); } catch (IllegalArgumentException ise) { assertTrue(ise.getMessage().contains("jdbcUrl is required with driverClassName")); } } @Test public void validateDriverAndUrl() { try { HikariConfig config = newHikariConfig(); config.setDriverClassName("com.zaxxer.hikari.mocks.StubDriver"); config.setJdbcUrl("jdbc:stub"); config.validate(); } catch (Throwable t) { fail(t.getMessage()); } } @Test public void validateBadDriver() { try { HikariConfig config = newHikariConfig(); config.setDriverClassName("invalid"); config.validate(); fail(); } catch (RuntimeException ise) { assertTrue(ise.getMessage().startsWith("Failed to load driver class invalid")); } } @Test public void validateInvalidConnectionTimeout() { try { HikariConfig config = newHikariConfig(); config.setConnectionTimeout(10L); fail(); } catch (IllegalArgumentException ise) { assertTrue(ise.getMessage().contains("connectionTimeout cannot be less than 250ms")); } } @Test public void validateInvalidValidationTimeout() { try { HikariConfig config = newHikariConfig(); config.setValidationTimeout(10L); fail(); } catch (IllegalArgumentException ise) { assertTrue(ise.getMessage().contains("validationTimeout cannot be less than 250ms")); } } @Test public void validateInvalidIdleTimeout() { try { HikariConfig config = newHikariConfig(); config.setIdleTimeout(-1L); fail("negative idle timeout accepted"); } catch (IllegalArgumentException ise) { assertTrue(ise.getMessage().contains("idleTimeout cannot be negative")); } } @Test public void validateIdleTimeoutTooSmall() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos, true); setSlf4jTargetStream(HikariConfig.class, ps); HikariConfig config = newHikariConfig(); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); config.setMinimumIdle(5); config.setIdleTimeout(TimeUnit.SECONDS.toMillis(5)); config.validate(); assertTrue(new String(baos.toByteArray()).contains("less than 10000ms")); } @Test public void validateIdleTimeoutExceedsLifetime() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos, true); setSlf4jTargetStream(HikariConfig.class, ps); HikariConfig config = newHikariConfig(); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); config.setMinimumIdle(5); config.setMaxLifetime(TimeUnit.MINUTES.toMillis(2)); config.setIdleTimeout(TimeUnit.MINUTES.toMillis(3)); config.validate(); String s = new String(baos.toByteArray()); assertTrue("idleTimeout is close to or more than maxLifetime, disabling it." + s + "*", s.contains("is close to or more than maxLifetime")); } @Test public void validateInvalidMinIdle() { try { HikariConfig config = newHikariConfig(); config.setMinimumIdle(-1); fail(); } catch (IllegalArgumentException ise) { assertTrue(ise.getMessage().contains("minimumIdle cannot be negative")); } } @Test public void validateInvalidMaxPoolSize() { try { HikariConfig config = newHikariConfig(); config.setMaximumPoolSize(0); fail(); } catch (IllegalArgumentException ise) { assertTrue(ise.getMessage().contains("maxPoolSize cannot be less than 1")); } } @Test public void validateInvalidLifetime() { try { HikariConfig config = newHikariConfig(); config.setConnectionTimeout(Integer.MAX_VALUE); config.setIdleTimeout(1000L); config.setMaxLifetime(-1L); config.setLeakDetectionThreshold(1000L); config.validate(); fail(); } catch (IllegalArgumentException ise) { // pass } } @Test public void validateInvalidLeakDetection() { try { HikariConfig config = newHikariConfig(); config.setLeakDetectionThreshold(1000L); config.validate(); fail(); } catch (IllegalArgumentException ise) { // pass } } @Test public void validateZeroConnectionTimeout() { try { HikariConfig config = newHikariConfig(); config.setConnectionTimeout(0); config.validate(); assertEquals(Integer.MAX_VALUE, config.getConnectionTimeout()); } catch (IllegalArgumentException ise) { // pass } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/pool/UnwrapTest.java ================================================ /* * Copyright (C) 2013 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.pool; import static com.zaxxer.hikari.pool.TestElf.newHikariConfig; import static com.zaxxer.hikari.pool.TestElf.getPool; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.sql.Connection; import java.sql.SQLException; import org.junit.Test; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.mocks.StubConnection; import com.zaxxer.hikari.mocks.StubDataSource; /** * @author Brett Wooldridge */ public class UnwrapTest { @Test public void testUnwrapConnection() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(1); config.setInitializationFailTimeout(0); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { ds.getConnection().close(); assertSame("Idle connections not as expected", 1, getPool(ds).getIdleConnections()); Connection connection = ds.getConnection(); assertNotNull(connection); StubConnection unwrapped = connection.unwrap(StubConnection.class); assertTrue("unwrapped connection is not instance of StubConnection: " + unwrapped, (unwrapped instanceof StubConnection)); } } @Test public void testUnwrapDataSource() throws SQLException { HikariConfig config = newHikariConfig(); config.setMinimumIdle(1); config.setMaximumPoolSize(1); config.setInitializationFailTimeout(0); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); try (HikariDataSource ds = new HikariDataSource(config)) { StubDataSource unwrap = ds.unwrap(StubDataSource.class); assertNotNull(unwrap); assertTrue(unwrap instanceof StubDataSource); assertTrue(ds.isWrapperFor(HikariDataSource.class)); assertTrue(ds.unwrap(HikariDataSource.class) instanceof HikariDataSource); assertFalse(ds.isWrapperFor(getClass())); try { ds.unwrap(getClass()); } catch (SQLException e) { assertTrue(e.getMessage().contains("Wrapped DataSource")); } } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/util/ClockSourceTest.java ================================================ /* * Copyright (C) 2016 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.util; import org.junit.Assert; import org.junit.Test; import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.MICROSECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; /** * * @author Brett Wooldridge */ public class ClockSourceTest { @Test public void testClockSourceDisplay() { ClockSource msSource = new ClockSource.MillisecondClockSource(); final long sTime = DAYS.toMillis(3) + HOURS.toMillis(9) + MINUTES.toMillis(24) + SECONDS.toMillis(18) + MILLISECONDS.toMillis(572); final long eTime = DAYS.toMillis(4) + HOURS.toMillis(9) + MINUTES.toMillis(55) + SECONDS.toMillis(23) + MILLISECONDS.toMillis(777); String ds1 = msSource.elapsedDisplayString0(sTime, eTime); Assert.assertEquals("1d31m5s205ms", ds1); final long eTime2 = DAYS.toMillis(3) + HOURS.toMillis(8) + MINUTES.toMillis(24) + SECONDS.toMillis(23) + MILLISECONDS.toMillis(777); String ds2 = msSource.elapsedDisplayString0(sTime, eTime2); Assert.assertEquals("-59m54s795ms", ds2); ClockSource nsSource = new ClockSource.NanosecondClockSource(); final long sTime2 = DAYS.toNanos(3) + HOURS.toNanos(9) + MINUTES.toNanos(24) + SECONDS.toNanos(18) + MILLISECONDS.toNanos(572) + MICROSECONDS.toNanos(324) + NANOSECONDS.toNanos(823); final long eTime3 = DAYS.toNanos(4) + HOURS.toNanos(19) + MINUTES.toNanos(55) + SECONDS.toNanos(23) + MILLISECONDS.toNanos(777) + MICROSECONDS.toNanos(0) + NANOSECONDS.toNanos(982); String ds3 = nsSource.elapsedDisplayString0(sTime2, eTime3); Assert.assertEquals("1d10h31m5s204ms676µs159ns", ds3); } } ================================================ FILE: src/test/java/com/zaxxer/hikari/util/DriverDataSourceTest.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.util; import org.junit.Test; import java.lang.reflect.Field; import java.time.Duration; import java.util.Arrays; import java.util.List; import java.util.Properties; import static org.junit.Assert.*; public class DriverDataSourceTest { @Test public void testDriverProperties() throws Exception { Properties properties = new Properties(); Duration timeout = Duration.ofSeconds(60); properties.put("timeout", timeout); var driverDataSource = new DriverDataSource("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", null, properties, "", ""); Field field = DriverDataSource.class.getDeclaredField("driverProperties"); field.setAccessible(true); Properties driverProperties = (Properties) field.get(driverDataSource); assertEquals(timeout, driverProperties.get("timeout")); } @Test public void testJdbcUrlLogging() { List urls = Arrays.asList( "jdbc:invalid://host/d_dlq?user=USER&password=SECRET", "jdbc:invalid://host/d_dlq?user=USER&truststorePassword=SECRET", "jdbc:invalid://host/d_dlq?a=b&password=SECRET&user=USER", "jdbc:invalid://host/d_dlq?a=b&sslpassword=SECRET&user=USER", "jdbc:invalid://host/d_dlq?a=b&sslpassword=SECRET&password=SECRET&user=USER", "jdbc:invalid://host/d_dlq?truststorePassword=SECRET;user=USER&password=SECRET#extra", "jdbc:invalid://host/d_dlq?sslpassword=SECRET&password=SECRET&trustPassword=SECRET&user=USER", "jdbc:invalid://host/d_dlq?password=SECRET#user=USER;extra" ); for (String url : urls) { testExceptionMessage(url); } } private void testExceptionMessage(String jdbcUrl) { try { new DriverDataSource(jdbcUrl, null, new Properties(), null, null); fail(); } catch (RuntimeException e) { String msg = e.getMessage(); assertTrue(msg.contains("jdbc:invalid://host/d_dlq")); assertTrue(msg.contains("user=USER")); assertFalse("Exception message should not contain password", msg.contains("SECRET")); } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/util/PropertyElfTest.java ================================================ package com.zaxxer.hikari.util; import org.junit.Test; import com.zaxxer.hikari.mocks.TestObject; import java.util.Properties; import static org.junit.Assert.*; public class PropertyElfTest { @Test public void setTargetFromProperties() throws Exception { Properties properties = new Properties(); properties.setProperty("string", "aString"); properties.setProperty("testObject", "com.zaxxer.hikari.mocks.TestObject"); properties.setProperty("shortRaw", "1"); properties.setProperty("charArray", "aCharArray"); TestObject testObject = new TestObject(); PropertyElf.setTargetFromProperties(testObject, properties); assertEquals("aString", testObject.getString()); assertEquals((short) 1, testObject.getShortRaw()); assertArrayEquals("aCharArray".toCharArray(), testObject.getCharArray()); assertEquals(com.zaxxer.hikari.mocks.TestObject.class, testObject.getTestObject().getClass()); assertNotSame(testObject, testObject.getTestObject()); } @Test public void setTargetFromPropertiesNotAClass() throws Exception { Properties properties = new Properties(); properties.setProperty("string", "aString"); properties.setProperty("testObject", "it is not a class"); TestObject testObject = new TestObject(); try { PropertyElf.setTargetFromProperties(testObject, properties); fail("Could never come here"); } catch (RuntimeException e) { assertEquals("argument type mismatch", e.getCause().getMessage()); } } @Test public void setStringArray() { Properties properties = new Properties(); TestObject testObject = new TestObject(); properties.setProperty("stringArray", "abc,123"); PropertyElf.setTargetFromProperties(testObject, properties); assertArrayEquals(new String[] {"abc", "123"}, testObject.getStringArray()); properties.setProperty("stringArray", "abc\\,123"); PropertyElf.setTargetFromProperties(testObject, properties); assertArrayEquals(new String[] {"abc,123"}, testObject.getStringArray()); properties.setProperty("stringArray", "abc\\\\,123"); PropertyElf.setTargetFromProperties(testObject, properties); assertArrayEquals(new String[] {"abc\\","123"}, testObject.getStringArray()); properties.setProperty("stringArray", ""); PropertyElf.setTargetFromProperties(testObject, properties); assertArrayEquals(new String[] {}, testObject.getStringArray()); properties.setProperty("stringArray", "abc,12\\3"); PropertyElf.setTargetFromProperties(testObject, properties); assertArrayEquals(new String[] {"abc","123"}, testObject.getStringArray()); properties.setProperty("stringArray", "abc,123\\"); assertThrows(RuntimeException.class, () -> PropertyElf.setTargetFromProperties(testObject, properties)); } @Test public void setIntArray() { Properties properties = new Properties(); TestObject testObject = new TestObject(); properties.setProperty("intArray", "1,2,3"); PropertyElf.setTargetFromProperties(testObject, properties); assertArrayEquals(new int[] {1,2,3}, testObject.getIntArray()); properties.setProperty("intArray", ""); PropertyElf.setTargetFromProperties(testObject, properties); assertArrayEquals(new int[] {}, testObject.getIntArray()); } } ================================================ FILE: src/test/java/com/zaxxer/hikari/util/TestFastList.java ================================================ /* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.zaxxer.hikari.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import java.sql.Statement; import java.util.ArrayList; import java.util.Iterator; import org.junit.Test; import com.zaxxer.hikari.mocks.StubStatement; public class TestFastList { @Test public void testAddRemove() { ArrayList verifyList = new ArrayList<>(); FastList list = new FastList<>(Statement.class); for (int i = 0; i < 32; i++) { StubStatement statement = new StubStatement(null); list.add(statement); verifyList.add(statement); } for (int i = 0; i < 32; i++) { assertNotNull("Element " + i + " was null but should be " + verifyList.get(i), list.get(0)); int size = list.size(); list.remove(verifyList.get(i)); assertSame(size - 1, list.size()); } } @Test public void testAddRemoveTail() { ArrayList verifyList = new ArrayList<>(); FastList list = new FastList<>(Statement.class); for (int i = 0; i < 32; i++) { StubStatement statement = new StubStatement(null); list.add(statement); verifyList.add(statement); } for (int i = 31; i >= 0; i--) { assertNotNull("Element " + i, list.get(i)); int size = list.size(); list.remove(verifyList.get(i)); assertSame(size - 1, list.size()); } } @Test public void testOverflow() { ArrayList verifyList = new ArrayList<>(); FastList list = new FastList<>(Statement.class); for (int i = 0; i < 100; i++) { StubStatement statement = new StubStatement(null); list.add(statement); verifyList.add(statement); } for (int i = 0; i < 100; i++) { assertNotNull("Element " + i, list.get(i)); assertSame(verifyList.get(i), list.get(i)); } } @Test public void testIterator() { FastList list = new FastList<>(Statement.class); for (int i = 0; i < 100; i++) { StubStatement statement = new StubStatement(null); list.add(statement); } Iterator iter = list.iterator(); for (int i = 0; i < list.size(); i++) { assertSame(list.get(i), iter.next()); } } @Test public void testClear() { FastList list = new FastList<>(Statement.class); for (int i = 0; i < 100; i++) { StubStatement statement = new StubStatement(null); list.add(statement); } assertNotEquals(0, list.size()); list.clear(); assertEquals(0, list.size()); // also check that all elements are now null for (int i = 0; i < 100; i++) { assertEquals(null, list.get(i)); } } @Test public void testRemoveLast() { FastList list = new FastList<>(Statement.class); Statement last = null; for (int i = 0; i < 100; i++) { StubStatement statement = new StubStatement(null); list.add(statement); last = statement; } assertEquals(last, list.removeLast()); assertEquals(99, list.size()); } @Test public void testPolyMorphism1() { class Foo implements Base2 { } class Bar extends Foo { } FastList list = new FastList<>(Base.class, 2); list.add(new Foo()); list.add(new Foo()); list.add(new Bar()); } interface Base { } interface Base2 extends Base { } } ================================================ FILE: src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java ================================================ /* * 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.util; import com.zaxxer.hikari.pool.TestElf.FauxWebClassLoader; 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.lang.ref.Reference; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.concurrent.CompletableFuture; import static com.zaxxer.hikari.pool.TestElf.isJava11; 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 */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class TomcatConcurrentBagLeakTest { @Test public void testConcurrentBagForLeaks() throws Exception { assumeTrue(!isJava11()); ClassLoader cl = new FauxWebClassLoader(); Class clazz = cl.loadClass(this.getClass().getName() + "$FauxWebContext"); Object fauxWebContext = clazz.getDeclaredConstructor().newInstance(); Method createConcurrentBag = clazz.getDeclaredMethod("createConcurrentBag"); createConcurrentBag.invoke(fauxWebContext); Field failureException = clazz.getDeclaredField("failureException"); Exception ex = (Exception) failureException.get(fauxWebContext); assertNull(ex); } @Test public void testConcurrentBagForLeaks2() throws Exception { assumeTrue(!isJava11()); ClassLoader cl = this.getClass().getClassLoader(); Class clazz = cl.loadClass(this.getClass().getName() + "$FauxWebContext"); Object fauxWebContext = clazz.getDeclaredConstructor().newInstance(); Method createConcurrentBag = clazz.getDeclaredMethod("createConcurrentBag"); createConcurrentBag.invoke(fauxWebContext); Field failureException = clazz.getDeclaredField("failureException"); Exception ex = (Exception) failureException.get(fauxWebContext); assertNotNull(ex); } public static class PoolEntry implements IConcurrentBagEntry { private int state; @Override public boolean compareAndSet(int expectState, int newState) { this.state = newState; return true; } @Override public void setState(int newState) { this.state = newState; } @Override public int getState() { return state; } } public static class FauxWebContext { private static final Logger log = LoggerFactory.getLogger(FauxWebContext.class); @SuppressWarnings("WeakerAccess") public Exception failureException; @SuppressWarnings({"ResultOfMethodCallIgnored"}) public void createConcurrentBag() throws InterruptedException { try (ConcurrentBag bag = new ConcurrentBag<>(x -> CompletableFuture.completedFuture(Boolean.TRUE))) { PoolEntry entry = new PoolEntry(); bag.add(entry); PoolEntry borrowed = bag.borrow(100, MILLISECONDS); bag.requite(borrowed); PoolEntry removed = bag.borrow(100, MILLISECONDS); bag.remove(removed); } checkThreadLocalsForLeaks(); } private void checkThreadLocalsForLeaks() { Thread[] threads = getThreads(); try { // Make the fields in the Thread class that store ThreadLocals // accessible Field threadLocalsField = Thread.class.getDeclaredField("threadLocals"); threadLocalsField.setAccessible(true); Field inheritableThreadLocalsField = Thread.class.getDeclaredField("inheritableThreadLocals"); inheritableThreadLocalsField.setAccessible(true); // Make the underlying array of ThreadLoad.ThreadLocalMap.Entry objects // accessible Class tlmClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap"); Field tableField = tlmClass.getDeclaredField("table"); tableField.setAccessible(true); Method expungeStaleEntriesMethod = tlmClass.getDeclaredMethod("expungeStaleEntries"); expungeStaleEntriesMethod.setAccessible(true); for (Thread thread : threads) { Object threadLocalMap; if (thread != null) { // Clear the first map threadLocalMap = threadLocalsField.get(thread); if (null != threadLocalMap) { expungeStaleEntriesMethod.invoke(threadLocalMap); checkThreadLocalMapForLeaks(threadLocalMap, tableField); } // Clear the second map threadLocalMap = inheritableThreadLocalsField.get(thread); if (null != threadLocalMap) { expungeStaleEntriesMethod.invoke(threadLocalMap); checkThreadLocalMapForLeaks(threadLocalMap, tableField); } } } } catch (Throwable t) { log.warn("Failed to check for ThreadLocal references for web application [{}]", getContextName(), t); failureException = new Exception(); } } private Object getContextName() { return this.getClass().getName(); } // THE FOLLOWING CODE COPIED FROM APACHE TOMCAT (2017/01/08) /** * Analyzes the given thread local map object. Also pass in the field that * points to the internal table to save re-calculating it on every * call to this method. */ private void checkThreadLocalMapForLeaks(Object map, Field internalTableField) throws IllegalAccessException, NoSuchFieldException { if (map != null) { Object[] table = (Object[]) internalTableField.get(map); if (table != null) { for (Object obj : table) { if (obj != null) { boolean keyLoadedByWebapp = false; boolean valueLoadedByWebapp = false; // Check the key Object key = ((Reference) obj).get(); if (this.equals(key) || loadedByThisOrChild(key)) { keyLoadedByWebapp = true; } // Check the value Field valueField = obj.getClass().getDeclaredField("value"); valueField.setAccessible(true); Object value = valueField.get(obj); if (this.equals(value) || loadedByThisOrChild(value)) { valueLoadedByWebapp = true; } if (keyLoadedByWebapp || valueLoadedByWebapp) { Object[] args = new Object[5]; args[0] = getContextName(); if (key != null) { args[1] = getPrettyClassName(key.getClass()); try { args[2] = key.toString(); } catch (Exception e) { log.warn("Unable to determine string representation of key of type [{}]", args[1], e); args[2] = "Unknown"; } } if (value != null) { args[3] = getPrettyClassName(value.getClass()); try { args[4] = value.toString(); } catch (Exception e) { log.warn("webappClassLoader.checkThreadLocalsForLeaks.badValue {}", args[3], e); args[4] = "Unknown"; } } if (valueLoadedByWebapp) { log.error("The web application [{}] created a ThreadLocal with key of type [{}] " + "(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) { 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); failureException = new Exception(); } 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); failureException = new Exception(); } } } } } } } /** * @param o object to test, may be null * @return true if o has been loaded by the current classloader * or one of its descendants. */ private boolean loadedByThisOrChild(Object o) { if (o == null) { return false; } Class clazz; if (o instanceof Class) { clazz = (Class) o; } else { clazz = o.getClass(); } ClassLoader cl = clazz.getClassLoader(); while (cl != null) { if (cl == this.getClass().getClassLoader()) { return true; } cl = cl.getParent(); } if (o instanceof Collection) { Iterator iter = ((Collection) o).iterator(); try { while (iter.hasNext()) { Object entry = iter.next(); if (loadedByThisOrChild(entry)) { return true; } } } catch (ConcurrentModificationException e) { log.warn("Failed to check for ThreadLocal references for web application [{}]", getContextName(), e); } } return false; } /* * Get the set of current threads as an array. */ private Thread[] getThreads() { // Get the current thread group ThreadGroup tg = Thread.currentThread().getThreadGroup(); // Find the root thread group try { while (tg.getParent() != null) { tg = tg.getParent(); } } catch (SecurityException se) { log.warn("Unable to obtain the parent for ThreadGroup [{}]. It will not be possible to check all threads for potential memory leaks", tg.getName(), se); } int threadCountGuess = tg.activeCount() + 50; Thread[] threads = new Thread[threadCountGuess]; int threadCountActual = tg.enumerate(threads); // Make sure we don't miss any threads while (threadCountActual == threadCountGuess) { threadCountGuess *= 2; threads = new Thread[threadCountGuess]; // Note tg.enumerate(Thread[]) silently ignores any threads that // can't fit into the array threadCountActual = tg.enumerate(threads); } return threads; } private String getPrettyClassName(Class clazz) { String name = clazz.getCanonicalName(); if (name == null) { name = clazz.getName(); } return name; } } } ================================================ FILE: src/test/java/com/zaxxer/hikari/util/UtilityElfTest.java ================================================ /* * Copyright (C) 2013, 2019 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 org.junit.Test; import static org.junit.Assert.assertEquals; public class UtilityElfTest { @Test public void shouldReturnValidTransactionIsolationLevel() { //Act int expectedLevel = UtilityElf.getTransactionIsolation("TRANSACTION_SQL_SERVER_SNAPSHOT_ISOLATION_LEVEL"); //Assert assertEquals(4096, expectedLevel); } @Test(expected = IllegalArgumentException.class) public void shouldThrowWhenInvalidTransactionNameGiven() { //Act UtilityElf.getTransactionIsolation("INVALID_TRANSACTION"); } @Test public void shouldReturnTransationIsolationLevelFromInteger() { int expectedLevel = UtilityElf.getTransactionIsolation("4096"); assertEquals(4096, expectedLevel); } @Test(expected = IllegalArgumentException.class) public void shouldThrowWhenInvalidTransactionIntegerGiven() { //Act UtilityElf.getTransactionIsolation("9999"); } @Test public void shouldCreateInstanceOfClassWithConstructorThatAcceptsSuperClassAndInterfaceAndClassOfArguments() { //Act UtilityElf.createInstance("com.zaxxer.hikari.util.UtilityElfTest$ClassZ", Object.class, new ClassB(), new ClassC(), new ClassD()); } public static class ClassA {} public static final class ClassB extends ClassA {} public interface InterfaceC {} public final static class ClassC implements InterfaceC {} public final static class ClassD {} public final static class ClassZ { public ClassZ(ClassA _superClassA, InterfaceC _interfaceC, ClassD _classD) {} } } ================================================ FILE: src/test/resources/duration-config.properties ================================================ connectionTimeout = 2000ms validationTimeout = 22s idleTimeout = 33m leakDetectionThreshold = 44h maxLifetime = 55d dataSourceClassName=com.zaxxer.hikari.mocks.StubDataSource dataSource.loginTimeout = 60000 ================================================ FILE: src/test/resources/hibernate.properties ================================================ hibernate.hikari.minimumIdle=5 hibernate.hikari.connectionTestQuery=SELECT 1 hibernate.hikari.dataSourceClassName=com.zaxxer.hikari.mocks.StubDataSource hibernate.connection.autocommit=false ================================================ FILE: src/test/resources/log4j2-test.xml ================================================ ================================================ FILE: src/test/resources/postgres_init_script.sql ================================================ CREATE SCHEMA IF NOT EXISTS test; ================================================ FILE: src/test/resources/propfile1.properties ================================================ minimumIdle=5 connectionTestQuery=SELECT 1 autoCommit=false dataSourceClassName=com.zaxxer.hikari.mocks.StubDataSource ================================================ FILE: src/test/resources/propfile2.properties ================================================ connectionTestQuery=SELECT 1 autoCommit=false dataSourceClassName=com.zaxxer.hikari.mocks.StubDataSource dataSource.user=test dataSource.password=test ================================================ FILE: src/test/resources/propfile3.properties ================================================ connectionTestQuery=SELECT 1 autoCommit=false dataSourceClassName=com.zaxxer.hikari.mocks.StubDataSource dataSource.user=test dataSource.password=test dataSource.url=jdbc:stub:foo