From 8fd73d250191b6e75e992b6b7bcb407d9eeeea06 Mon Sep 17 00:00:00 2001 From: Nitin Date: Mon, 20 Jul 2015 20:54:14 +0530 Subject: [PATCH] HikariPool: Log consitency PoolElf: I think two more round trips to db/driver is overkill (for pool to support bad driver) especially jdbc spec reads 'closing statement must close any current result set'. It is already slow (compare to others) having network timeout as well as query timeout to validate connection! --- .../com/zaxxer/hikari/pool/HikariPool.java | 1304 ++++++++--------- .../java/com/zaxxer/hikari/pool/PoolElf.java | 1044 +++++++------ 2 files changed, 1172 insertions(+), 1176 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/pool/HikariPool.java b/src/main/java/com/zaxxer/hikari/pool/HikariPool.java index 2677103e..1897d7ec 100644 --- a/src/main/java/com/zaxxer/hikari/pool/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/pool/HikariPool.java @@ -1,652 +1,652 @@ -/* - * 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.util.ConcurrentBag.IConcurrentBagEntry.STATE_IN_USE; -import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_NOT_IN_USE; -import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_REMOVED; -import static com.zaxxer.hikari.util.UtilityElf.createThreadPoolExecutor; -import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; - -import java.sql.Connection; -import java.sql.SQLException; -import java.sql.SQLTimeoutException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -import javax.sql.DataSource; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.codahale.metrics.MetricRegistry; -import com.codahale.metrics.health.HealthCheckRegistry; -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariPoolMXBean; -import com.zaxxer.hikari.metrics.CodahaleHealthChecker; -import com.zaxxer.hikari.metrics.CodahaleMetricsTrackerFactory; -import com.zaxxer.hikari.metrics.MetricsTracker; -import com.zaxxer.hikari.metrics.MetricsTracker.MetricsContext; -import com.zaxxer.hikari.metrics.MetricsTrackerFactory; -import com.zaxxer.hikari.metrics.PoolStats; -import com.zaxxer.hikari.proxy.ConnectionProxy; -import com.zaxxer.hikari.proxy.IHikariConnectionProxy; -import com.zaxxer.hikari.proxy.ProxyFactory; -import com.zaxxer.hikari.util.ClockSource; -import com.zaxxer.hikari.util.ConcurrentBag; -import com.zaxxer.hikari.util.ConcurrentBag.IBagStateListener; -import com.zaxxer.hikari.util.DefaultThreadFactory; -import com.zaxxer.hikari.util.PropertyElf; - -/** - * This is the primary connection pool class that provides the basic - * pooling behavior for HikariCP. - * - * @author Brett Wooldridge - */ -public class HikariPool implements HikariPoolMXBean, IBagStateListener -{ - final Logger LOGGER = LoggerFactory.getLogger(getClass()); - - private static final ClockSource clockSource = ClockSource.INSTANCE; - - private final long ALIVE_BYPASS_WINDOW_MS = Long.getLong("com.zaxxer.hikari.aliveBypassWindow", TimeUnit.SECONDS.toMillis(1)); - private final long HOUSEKEEPING_PERIOD_MS = Long.getLong("com.zaxxer.hikari.housekeeping.periodMs", TimeUnit.SECONDS.toMillis(30)); - - private static final int POOL_NORMAL = 0; - private static final int POOL_SUSPENDED = 1; - private static final int POOL_SHUTDOWN = 2; - - final PoolElf poolElf; - final HikariConfig config; - final ConcurrentBag connectionBag; - final ScheduledThreadPoolExecutor houseKeepingExecutorService; - - private final AtomicInteger totalConnections; - private final ThreadPoolExecutor addConnectionExecutor; - private final ThreadPoolExecutor closeConnectionExecutor; - - private volatile int poolState; - private long connectionTimeout; - - private final String poolName; - private final LeakTask leakTask; - private final DataSource dataSource; - private final SuspendResumeLock suspendResumeLock; - private final AtomicReference lastConnectionFailure; - - private volatile MetricsTracker metricsTracker; - private boolean isRecordMetrics; - - /** - * Construct a HikariPool with the specified configuration. - * - * @param config a HikariConfig instance - */ - public HikariPool(final HikariConfig config) - { - this.config = config; - - this.poolElf = new PoolElf(config); - this.dataSource = poolElf.initializeDataSource(); - - this.poolName = config.getPoolName(); - this.connectionBag = new ConcurrentBag<>(this); - this.totalConnections = new AtomicInteger(); - this.connectionTimeout = config.getConnectionTimeout(); - this.lastConnectionFailure = new AtomicReference<>(); - this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock(true) : SuspendResumeLock.FAUX_LOCK; - - this.addConnectionExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), "Hikari connection filler (pool " + poolName + ")", config.getThreadFactory(), new ThreadPoolExecutor.DiscardPolicy()); - this.closeConnectionExecutor = createThreadPoolExecutor(4, "Hikari connection closer (pool " + poolName + ")", config.getThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); - - if (config.getScheduledExecutorService() == null) { - ThreadFactory threadFactory = config.getThreadFactory() != null ? config.getThreadFactory() : new DefaultThreadFactory("Hikari housekeeper (pool " + poolName + ")", true); - this.houseKeepingExecutorService = new ScheduledThreadPoolExecutor(1, threadFactory, new ThreadPoolExecutor.DiscardPolicy()); - this.houseKeepingExecutorService.scheduleAtFixedRate(new HouseKeeper(), HOUSEKEEPING_PERIOD_MS, HOUSEKEEPING_PERIOD_MS, TimeUnit.MILLISECONDS); - this.houseKeepingExecutorService.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); - this.houseKeepingExecutorService.setRemoveOnCancelPolicy(true); - } - else { - this.houseKeepingExecutorService = config.getScheduledExecutorService(); - } - - this.leakTask = new LeakTask(config.getLeakDetectionThreshold(), houseKeepingExecutorService); - - if (config.getMetricsTrackerFactory() != null) { - setMetricsTrackerFactory(config.getMetricsTrackerFactory()); - } - else { - setMetricRegistry(config.getMetricRegistry()); - } - - setHealthCheckRegistry(config.getHealthCheckRegistry()); - - poolElf.registerMBeans(this); - - PropertyElf.flushCaches(); - - initializeConnections(); - } - - /** - * 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 final 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 final Connection getConnection(final long hardTimeout) throws SQLException - { - suspendResumeLock.acquire(); - final long startTime = clockSource.currentTime(); - - try { - long timeout = hardTimeout; - final MetricsContext metricsContext = (isRecordMetrics ? metricsTracker.recordConnectionRequest() : MetricsTracker.NO_CONTEXT); - do { - final PoolBagEntry bagEntry = connectionBag.borrow(timeout, TimeUnit.MILLISECONDS); - if (bagEntry == null) { - break; // We timed out... break and throw exception - } - - final long now = clockSource.currentTime(); - if (bagEntry.evicted || (clockSource.elapsedMillis(bagEntry.lastAccess, now) > ALIVE_BYPASS_WINDOW_MS && !poolElf.isConnectionAlive(bagEntry.connection, lastConnectionFailure))) { - closeConnection(bagEntry, "(connection evicted or dead)"); // Throw away the dead connection and try again - timeout = hardTimeout - clockSource.elapsedMillis(startTime, now); - } - else { - metricsContext.setConnectionLastOpen(bagEntry, now); - metricsContext.stop(); - return ProxyFactory.getProxyConnection(bagEntry, leakTask.start(bagEntry), now); - } - } - while (timeout > 0L); - } - catch (InterruptedException e) { - throw new SQLException("Interrupted during connection acquisition", e); - } - finally { - suspendResumeLock.release(); - } - - logPoolState("Timeout failure "); - String sqlState = null; - final Throwable originalException = lastConnectionFailure.getAndSet(null); - if (originalException instanceof SQLException) { - sqlState = ((SQLException) originalException).getSQLState(); - } - throw new SQLTimeoutException(String.format("Timeout after %dms of waiting for a connection.", clockSource.elapsedMillis(startTime)), sqlState, originalException); - } - - /** - * Release a connection back to the pool, or permanently close it if it is broken. - * - * @param bagEntry the PoolBagEntry to release back to the pool - */ - public final void releaseConnection(final PoolBagEntry bagEntry) - { - metricsTracker.recordConnectionUsage(bagEntry); - - if (bagEntry.evicted) { - closeConnection(bagEntry, "(connection broken or evicted)"); - } - else { - connectionBag.requite(bagEntry); - } - } - - /** - * Shutdown the pool, closing all idle connections and aborting or closing - * active connections. - * - * @throws InterruptedException thrown if the thread is interrupted during shutdown - */ - public final synchronized void shutdown() throws InterruptedException - { - try { - poolState = POOL_SHUTDOWN; - - LOGGER.info("Hikari pool {} is shutting down.", poolName); - logPoolState("Before shutdown "); - - connectionBag.close(); - softEvictConnections(); - addConnectionExecutor.shutdownNow(); - addConnectionExecutor.awaitTermination(5L, TimeUnit.SECONDS); - if (config.getScheduledExecutorService() == null) { - houseKeepingExecutorService.shutdown(); - houseKeepingExecutorService.awaitTermination(5L, TimeUnit.SECONDS); - } - - final ExecutorService assassinExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), "Hikari connection assassin", - config.getThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); - try { - final long start = clockSource.currentTime(); - do { - softEvictConnections(); - abortActiveConnections(assassinExecutor); - } - while (getTotalConnections() > 0 && clockSource.elapsedMillis(start) < TimeUnit.SECONDS.toMillis(5)); - } finally { - assassinExecutor.shutdown(); - assassinExecutor.awaitTermination(5L, TimeUnit.SECONDS); - } - - poolElf.shutdownTimeoutExecutor(); - closeConnectionExecutor.shutdown(); - closeConnectionExecutor.awaitTermination(5L, TimeUnit.SECONDS); - } - finally { - logPoolState("After shutdown "); - - poolElf.unregisterMBeans(); - metricsTracker.close(); - } - } - - /** - * Evict a connection from the pool. - * - * @param proxyConnection the connection to evict - */ - public final void evictConnection(IHikariConnectionProxy proxyConnection) - { - closeConnection(proxyConnection.getPoolBagEntry(), "(connection evicted by user)"); - } - - /** - * Get the wrapped DataSource. - * - * @return the wrapped DataSource - */ - public final DataSource getDataSource() - { - return dataSource; - } - - public void setMetricRegistry(Object metricRegistry) - { - this.isRecordMetrics = metricRegistry != null; - if (isRecordMetrics) { - setMetricsTrackerFactory(new CodahaleMetricsTrackerFactory((MetricRegistry) metricRegistry)); - } - else { - setMetricsTrackerFactory(null); - } - } - - public void setMetricsTrackerFactory(MetricsTrackerFactory metricsTrackerFactory) - { - this.isRecordMetrics = metricsTrackerFactory != null; - if (isRecordMetrics) { - this.metricsTracker = metricsTrackerFactory.create(config.getPoolName(), getPoolStats()); - } - else { - this.metricsTracker = new MetricsTracker(); - } - } - - public void setHealthCheckRegistry(Object healthCheckRegistry) - { - if (healthCheckRegistry != null) { - CodahaleHealthChecker.registerHealthChecks(this, config, (HealthCheckRegistry) healthCheckRegistry); - } - } - - /** - * Log the current pool state at debug level. - * - * @param prefix an optional prefix to prepend the log message - */ - public final void logPoolState(String... prefix) - { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("{}pool {} stats (total={}, active={}, idle={}, waiting={})", - (prefix.length > 0 ? prefix[0] : ""), poolName, - getTotalConnections(), getActiveConnections(), getIdleConnections(), getThreadsAwaitingConnection()); - } - } - - /** {@inheritDoc} */ - @Override - public String toString() - { - return poolName; - } - - // *********************************************************************** - // IBagStateListener callback - // *********************************************************************** - - /** {@inheritDoc} */ - @Override - public Future addBagItem() - { - FutureTask future = new FutureTask<>(new Runnable() { - @Override - public void run() - { - long sleepBackoff = 200L; - final int minimumIdle = config.getMinimumIdle(); - final int maxPoolSize = config.getMaximumPoolSize(); - while (poolState == POOL_NORMAL && totalConnections.get() < maxPoolSize && getIdleConnections() <= minimumIdle && !addConnection()) { - // If we got into the loop, addConnection() failed, so we sleep and retry - quietlySleep(sleepBackoff); - sleepBackoff = Math.min(connectionTimeout / 2, (long) ((double) sleepBackoff * 1.5)); - } - } - }, true); - - addConnectionExecutor.execute(future); - return future; - } - - // *********************************************************************** - // HikariPoolMBean methods - // *********************************************************************** - - /** {@inheritDoc} */ - @Override - public final int getActiveConnections() - { - return connectionBag.getCount(STATE_IN_USE); - } - - /** {@inheritDoc} */ - @Override - public final int getIdleConnections() - { - return connectionBag.getCount(STATE_NOT_IN_USE); - } - - /** {@inheritDoc} */ - @Override - public final int getTotalConnections() - { - return connectionBag.size() - connectionBag.getCount(STATE_REMOVED); - } - - /** {@inheritDoc} */ - @Override - public final int getThreadsAwaitingConnection() - { - return connectionBag.getPendingQueue(); - } - - /** {@inheritDoc} */ - @Override - public void softEvictConnections() - { - for (PoolBagEntry bagEntry : connectionBag.values()) { - bagEntry.evicted = true; - if (connectionBag.reserve(bagEntry)) { - closeConnection(bagEntry, "(connection evicted by user)"); - } - } - } - - /** {@inheritDoc} */ - @Override - public final synchronized void suspendPool() - { - if (suspendResumeLock == SuspendResumeLock.FAUX_LOCK) { - throw new IllegalStateException("Pool " + poolName + " is not suspendable"); - } - else if (poolState != POOL_SUSPENDED) { - suspendResumeLock.suspend(); - poolState = POOL_SUSPENDED; - } - } - - /** {@inheritDoc} */ - @Override - public final synchronized void resumePool() - { - if (poolState == POOL_SUSPENDED) { - poolState = POOL_NORMAL; - fillPool(); - suspendResumeLock.resume(); - } - } - - // *********************************************************************** - // Package methods - // *********************************************************************** - - /** - * Permanently close the real (underlying) connection (eat any exception). - * - * @param bagEntry the connection to actually close - */ - void closeConnection(final PoolBagEntry bagEntry, final String closureReason) - { - final Connection connection = bagEntry.connection; - bagEntry.connection = null; - bagEntry.cancelMaxLifeTermination(); - if (connectionBag.remove(bagEntry)) { - final int tc = totalConnections.decrementAndGet(); - if (tc < 0) { - LOGGER.warn("{} - Internal accounting inconsistency, totalConnections={}", poolName, tc, new Exception()); - } - - closeConnectionExecutor.execute(new Runnable() { - @Override - public void run() { - poolElf.quietlyCloseConnection(connection, closureReason); - } - }); - } - } - - // *********************************************************************** - // Private methods - // *********************************************************************** - - /** - * Create and add a single connection to the pool. - */ - private boolean addConnection() - { - // Speculative increment of totalConnections with expectation of success - if (totalConnections.incrementAndGet() > config.getMaximumPoolSize()) { - totalConnections.decrementAndGet(); // Pool is maxed out, so undo speculative increment of totalConnections - lastConnectionFailure.set(new SQLException("Hikari pool " + poolName +" is at maximum capacity")); - return true; - } - - Connection connection = null; - try { - String username = config.getUsername(); - String password = config.getPassword(); - - connection = (username == null && password == null) ? dataSource.getConnection() : dataSource.getConnection(username, password); - poolElf.setupConnection(connection, connectionTimeout); - - connectionBag.add(new PoolBagEntry(connection, this)); - lastConnectionFailure.set(null); - LOGGER.debug("{} - Connection {} added to pool", poolName, connection); - - return true; - } - catch (Exception e) { - totalConnections.decrementAndGet(); // We failed, so undo speculative increment of totalConnections - lastConnectionFailure.set(e); - if (poolState == POOL_NORMAL) { - LOGGER.debug("{} - Connection attempt to database failed", poolName, e); - } - poolElf.quietlyCloseConnection(connection, "(exception during connection creation)"); - return false; - } - } - - /** - * Fill pool up from current idle connections (as they are perceived at the point of execution) to minimumIdle connections. - */ - private void fillPool() - { - final int connectionsToAdd = Math.min(config.getMaximumPoolSize() - totalConnections.get(), config.getMinimumIdle() - getIdleConnections()); - for (int i = 0; i < connectionsToAdd; i++) { - addBagItem(); - } - - if (connectionsToAdd > 0 && LOGGER.isDebugEnabled()) { - addConnectionExecutor.execute(new Runnable() { - @Override - public void run() { - logPoolState("After fill "); - } - }); - } - } - - /** - * Attempt to abort() active connections, or close() them. - */ - private void abortActiveConnections(final ExecutorService assassinExecutor) - { - for (PoolBagEntry bagEntry : connectionBag.values(STATE_IN_USE)) { - try { - bagEntry.aborted = bagEntry.evicted = true; - bagEntry.connection.abort(assassinExecutor); - } - catch (Throwable e) { - poolElf.quietlyCloseConnection(bagEntry.connection, "(connection aborted during shutdown)"); - } - finally { - bagEntry.connection = null; - if (connectionBag.remove(bagEntry)) { - totalConnections.decrementAndGet(); - } - } - } - } - - /** - * Fill the pool up to the minimum size. - */ - private void initializeConnections() - { - if (config.isInitializationFailFast()) { - try { - if (!addConnection()) { - throw lastConnectionFailure.getAndSet(null); - } - - ConnectionProxy connection = (ConnectionProxy) getConnection(); - connection.getPoolBagEntry().evicted = (config.getMinimumIdle() == 0); - connection.close(); - } - catch (Throwable e) { - try { - shutdown(); - } - catch (Throwable ex) { - e.addSuppressed(ex); - } - - throw new PoolInitializationException(e); - } - } - - fillPool(); - } - - private PoolStats getPoolStats() - { - return new PoolStats(TimeUnit.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(); - } - }; - } - - // *********************************************************************** - // Non-anonymous Inner-classes - // *********************************************************************** - - /** - * The house keeping task to retire idle connections. - */ - private class HouseKeeper implements Runnable - { - private volatile long previous = clockSource.currentTime(); - - @Override - public void run() - { - // refresh timeouts in case they changed via MBean - connectionTimeout = config.getConnectionTimeout(); - poolElf.setValidationTimeout(config.getValidationTimeout()); - leakTask.updateLeakDetectionThreshold(config.getLeakDetectionThreshold()); - - final long now = clockSource.currentTime(); - final long idleTimeout = config.getIdleTimeout(); - - // Detect retrograde time as well as forward leaps of unacceptable duration - if (now < previous || now > clockSource.plusMillis(previous, (2 * HOUSEKEEPING_PERIOD_MS))) { - LOGGER.warn("{} - Unusual system clock change detected, soft-evicting connections from pool.", poolName); - softEvictConnections(); - fillPool(); - return; - } - - previous = now; - - logPoolState("Before cleanup "); - for (PoolBagEntry bagEntry : connectionBag.values(STATE_NOT_IN_USE)) { - if (connectionBag.reserve(bagEntry)) { - if (bagEntry.evicted) { - closeConnection(bagEntry, "(connection evicted)"); - } - else if (idleTimeout > 0L && clockSource.elapsedMillis(bagEntry.lastAccess, now) > idleTimeout) { - closeConnection(bagEntry, "(connection passed idleTimeout)"); - } - else { - connectionBag.unreserve(bagEntry); - } - } - } - - logPoolState("After cleanup "); - - fillPool(); // Try to maintain minimum connections - } - } -} +/* + * 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.util.ConcurrentBag.IConcurrentBagEntry.STATE_IN_USE; +import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_NOT_IN_USE; +import static com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry.STATE_REMOVED; +import static com.zaxxer.hikari.util.UtilityElf.createThreadPoolExecutor; +import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLTimeoutException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import javax.sql.DataSource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.health.HealthCheckRegistry; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariPoolMXBean; +import com.zaxxer.hikari.metrics.CodahaleHealthChecker; +import com.zaxxer.hikari.metrics.CodahaleMetricsTrackerFactory; +import com.zaxxer.hikari.metrics.MetricsTracker; +import com.zaxxer.hikari.metrics.MetricsTracker.MetricsContext; +import com.zaxxer.hikari.metrics.MetricsTrackerFactory; +import com.zaxxer.hikari.metrics.PoolStats; +import com.zaxxer.hikari.proxy.ConnectionProxy; +import com.zaxxer.hikari.proxy.IHikariConnectionProxy; +import com.zaxxer.hikari.proxy.ProxyFactory; +import com.zaxxer.hikari.util.ClockSource; +import com.zaxxer.hikari.util.ConcurrentBag; +import com.zaxxer.hikari.util.ConcurrentBag.IBagStateListener; +import com.zaxxer.hikari.util.DefaultThreadFactory; +import com.zaxxer.hikari.util.PropertyElf; + +/** + * This is the primary connection pool class that provides the basic + * pooling behavior for HikariCP. + * + * @author Brett Wooldridge + */ +public class HikariPool implements HikariPoolMXBean, IBagStateListener +{ + final Logger LOGGER = LoggerFactory.getLogger(getClass()); + + private static final ClockSource clockSource = ClockSource.INSTANCE; + + private final long ALIVE_BYPASS_WINDOW_MS = Long.getLong("com.zaxxer.hikari.aliveBypassWindow", TimeUnit.SECONDS.toMillis(1)); + private final long HOUSEKEEPING_PERIOD_MS = Long.getLong("com.zaxxer.hikari.housekeeping.periodMs", TimeUnit.SECONDS.toMillis(30)); + + private static final int POOL_NORMAL = 0; + private static final int POOL_SUSPENDED = 1; + private static final int POOL_SHUTDOWN = 2; + + final PoolElf poolElf; + final HikariConfig config; + final ConcurrentBag connectionBag; + final ScheduledThreadPoolExecutor houseKeepingExecutorService; + + private final AtomicInteger totalConnections; + private final ThreadPoolExecutor addConnectionExecutor; + private final ThreadPoolExecutor closeConnectionExecutor; + + private volatile int poolState; + private long connectionTimeout; + + private final String poolName; + private final LeakTask leakTask; + private final DataSource dataSource; + private final SuspendResumeLock suspendResumeLock; + private final AtomicReference lastConnectionFailure; + + private volatile MetricsTracker metricsTracker; + private boolean isRecordMetrics; + + /** + * Construct a HikariPool with the specified configuration. + * + * @param config a HikariConfig instance + */ + public HikariPool(final HikariConfig config) + { + this.config = config; + + this.poolElf = new PoolElf(config); + this.dataSource = poolElf.initializeDataSource(); + + this.poolName = config.getPoolName(); + this.connectionBag = new ConcurrentBag<>(this); + this.totalConnections = new AtomicInteger(); + this.connectionTimeout = config.getConnectionTimeout(); + this.lastConnectionFailure = new AtomicReference<>(); + this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock(true) : SuspendResumeLock.FAUX_LOCK; + + this.addConnectionExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), "Hikari connection filler (pool " + poolName + ")", config.getThreadFactory(), new ThreadPoolExecutor.DiscardPolicy()); + this.closeConnectionExecutor = createThreadPoolExecutor(4, "Hikari connection closer (pool " + poolName + ")", config.getThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); + + if (config.getScheduledExecutorService() == null) { + ThreadFactory threadFactory = config.getThreadFactory() != null ? config.getThreadFactory() : new DefaultThreadFactory("Hikari housekeeper (pool " + poolName + ")", true); + this.houseKeepingExecutorService = new ScheduledThreadPoolExecutor(1, threadFactory, new ThreadPoolExecutor.DiscardPolicy()); + this.houseKeepingExecutorService.scheduleAtFixedRate(new HouseKeeper(), HOUSEKEEPING_PERIOD_MS, HOUSEKEEPING_PERIOD_MS, TimeUnit.MILLISECONDS); + this.houseKeepingExecutorService.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + this.houseKeepingExecutorService.setRemoveOnCancelPolicy(true); + } + else { + this.houseKeepingExecutorService = config.getScheduledExecutorService(); + } + + this.leakTask = new LeakTask(config.getLeakDetectionThreshold(), houseKeepingExecutorService); + + if (config.getMetricsTrackerFactory() != null) { + setMetricsTrackerFactory(config.getMetricsTrackerFactory()); + } + else { + setMetricRegistry(config.getMetricRegistry()); + } + + setHealthCheckRegistry(config.getHealthCheckRegistry()); + + poolElf.registerMBeans(this); + + PropertyElf.flushCaches(); + + initializeConnections(); + } + + /** + * 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 final 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 final Connection getConnection(final long hardTimeout) throws SQLException + { + suspendResumeLock.acquire(); + final long startTime = clockSource.currentTime(); + + try { + long timeout = hardTimeout; + final MetricsContext metricsContext = (isRecordMetrics ? metricsTracker.recordConnectionRequest() : MetricsTracker.NO_CONTEXT); + do { + final PoolBagEntry bagEntry = connectionBag.borrow(timeout, TimeUnit.MILLISECONDS); + if (bagEntry == null) { + break; // We timed out... break and throw exception + } + + final long now = clockSource.currentTime(); + if (bagEntry.evicted || (clockSource.elapsedMillis(bagEntry.lastAccess, now) > ALIVE_BYPASS_WINDOW_MS && !poolElf.isConnectionAlive(bagEntry.connection, lastConnectionFailure))) { + closeConnection(bagEntry, "(connection evicted or dead)"); // Throw away the dead connection and try again + timeout = hardTimeout - clockSource.elapsedMillis(startTime, now); + } + else { + metricsContext.setConnectionLastOpen(bagEntry, now); + metricsContext.stop(); + return ProxyFactory.getProxyConnection(bagEntry, leakTask.start(bagEntry), now); + } + } + while (timeout > 0L); + } + catch (InterruptedException e) { + throw new SQLException(poolName + " - Interrupted during connection acquisition", e); + } + finally { + suspendResumeLock.release(); + } + + logPoolState("Timeout failure "); + String sqlState = null; + final Throwable originalException = lastConnectionFailure.getAndSet(null); + if (originalException instanceof SQLException) { + sqlState = ((SQLException) originalException).getSQLState(); + } + throw new SQLTimeoutException(poolName + " - Cannot acquire connection, Timeout after " + clockSource.elapsedMillis(startTime) + "ms.", sqlState, originalException); + } + + /** + * Release a connection back to the pool, or permanently close it if it is broken. + * + * @param bagEntry the PoolBagEntry to release back to the pool + */ + public final void releaseConnection(final PoolBagEntry bagEntry) + { + metricsTracker.recordConnectionUsage(bagEntry); + + if (bagEntry.evicted) { + closeConnection(bagEntry, "(connection broken or evicted)"); + } + else { + connectionBag.requite(bagEntry); + } + } + + /** + * Shutdown the pool, closing all idle connections and aborting or closing + * active connections. + * + * @throws InterruptedException thrown if the thread is interrupted during shutdown + */ + public final synchronized void shutdown() throws InterruptedException + { + try { + poolState = POOL_SHUTDOWN; + + LOGGER.info("{} - is closing down.", poolName); + logPoolState("Before closing "); + + connectionBag.close(); + softEvictConnections(); + addConnectionExecutor.shutdownNow(); + addConnectionExecutor.awaitTermination(5L, TimeUnit.SECONDS); + if (config.getScheduledExecutorService() == null) { + houseKeepingExecutorService.shutdown(); + houseKeepingExecutorService.awaitTermination(5L, TimeUnit.SECONDS); + } + + final ExecutorService assassinExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), "Hikari connection assassin", + config.getThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); + try { + final long start = clockSource.currentTime(); + do { + softEvictConnections(); + abortActiveConnections(assassinExecutor); + } + while (getTotalConnections() > 0 && clockSource.elapsedMillis(start) < TimeUnit.SECONDS.toMillis(5)); + } finally { + assassinExecutor.shutdown(); + assassinExecutor.awaitTermination(5L, TimeUnit.SECONDS); + } + + poolElf.shutdownTimeoutExecutor(); + closeConnectionExecutor.shutdown(); + closeConnectionExecutor.awaitTermination(5L, TimeUnit.SECONDS); + } + finally { + logPoolState("After closing "); + + poolElf.unregisterMBeans(); + metricsTracker.close(); + } + } + + /** + * Evict a connection from the pool. + * + * @param proxyConnection the connection to evict + */ + public final void evictConnection(IHikariConnectionProxy proxyConnection) + { + closeConnection(proxyConnection.getPoolBagEntry(), "(connection evicted by user)"); + } + + /** + * Get the wrapped DataSource. + * + * @return the wrapped DataSource + */ + public final DataSource getDataSource() + { + return dataSource; + } + + public void setMetricRegistry(Object metricRegistry) + { + this.isRecordMetrics = metricRegistry != null; + if (isRecordMetrics) { + setMetricsTrackerFactory(new CodahaleMetricsTrackerFactory((MetricRegistry) metricRegistry)); + } + else { + setMetricsTrackerFactory(null); + } + } + + public void setMetricsTrackerFactory(MetricsTrackerFactory metricsTrackerFactory) + { + this.isRecordMetrics = metricsTrackerFactory != null; + if (isRecordMetrics) { + this.metricsTracker = metricsTrackerFactory.create(config.getPoolName(), getPoolStats()); + } + else { + this.metricsTracker = new MetricsTracker(); + } + } + + public void setHealthCheckRegistry(Object healthCheckRegistry) + { + if (healthCheckRegistry != null) { + CodahaleHealthChecker.registerHealthChecks(this, config, (HealthCheckRegistry) healthCheckRegistry); + } + } + + /** + * Log the current pool state at debug level. + * + * @param prefix an optional prefix to prepend the log message + */ + public final void logPoolState(String... prefix) + { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{}pool {} stats (total={}, active={}, idle={}, waiting={})", + (prefix.length > 0 ? prefix[0] : ""), poolName, + getTotalConnections(), getActiveConnections(), getIdleConnections(), getThreadsAwaitingConnection()); + } + } + + /** {@inheritDoc} */ + @Override + public String toString() + { + return poolName; + } + + // *********************************************************************** + // IBagStateListener callback + // *********************************************************************** + + /** {@inheritDoc} */ + @Override + public Future addBagItem() + { + FutureTask future = new FutureTask<>(new Runnable() { + @Override + public void run() + { + long sleepBackoff = 200L; + final int minimumIdle = config.getMinimumIdle(); + final int maxPoolSize = config.getMaximumPoolSize(); + while (poolState == POOL_NORMAL && totalConnections.get() < maxPoolSize && getIdleConnections() <= minimumIdle && !addConnection()) { + // If we got into the loop, addConnection() failed, so we sleep and retry + quietlySleep(sleepBackoff); + sleepBackoff = Math.min(connectionTimeout / 2, (long) ((double) sleepBackoff * 1.5)); + } + } + }, true); + + addConnectionExecutor.execute(future); + return future; + } + + // *********************************************************************** + // HikariPoolMBean methods + // *********************************************************************** + + /** {@inheritDoc} */ + @Override + public final int getActiveConnections() + { + return connectionBag.getCount(STATE_IN_USE); + } + + /** {@inheritDoc} */ + @Override + public final int getIdleConnections() + { + return connectionBag.getCount(STATE_NOT_IN_USE); + } + + /** {@inheritDoc} */ + @Override + public final int getTotalConnections() + { + return connectionBag.size() - connectionBag.getCount(STATE_REMOVED); + } + + /** {@inheritDoc} */ + @Override + public final int getThreadsAwaitingConnection() + { + return connectionBag.getPendingQueue(); + } + + /** {@inheritDoc} */ + @Override + public void softEvictConnections() + { + for (PoolBagEntry bagEntry : connectionBag.values()) { + bagEntry.evicted = true; + if (connectionBag.reserve(bagEntry)) { + closeConnection(bagEntry, "(connection evicted by user)"); + } + } + } + + /** {@inheritDoc} */ + @Override + public final 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 final synchronized void resumePool() + { + if (poolState == POOL_SUSPENDED) { + poolState = POOL_NORMAL; + fillPool(); + suspendResumeLock.resume(); + } + } + + // *********************************************************************** + // Package methods + // *********************************************************************** + + /** + * Permanently close the real (underlying) connection (eat any exception). + * + * @param bagEntry the connection to actually close + */ + void closeConnection(final PoolBagEntry bagEntry, final String closureReason) + { + final Connection connection = bagEntry.connection; + bagEntry.connection = null; + bagEntry.cancelMaxLifeTermination(); + if (connectionBag.remove(bagEntry)) { + final int tc = totalConnections.decrementAndGet(); + if (tc < 0) { + LOGGER.warn("{} - Internal accounting inconsistency, totalConnections={}", poolName, tc, new Exception()); + } + + closeConnectionExecutor.execute(new Runnable() { + @Override + public void run() { + poolElf.quietlyCloseConnection(connection, closureReason); + } + }); + } + } + + // *********************************************************************** + // Private methods + // *********************************************************************** + + /** + * Create and add a single connection to the pool. + */ + private boolean addConnection() + { + // Speculative increment of totalConnections with expectation of success + if (totalConnections.incrementAndGet() > config.getMaximumPoolSize()) { + totalConnections.decrementAndGet(); // Pool is maxed out, so undo speculative increment of totalConnections + lastConnectionFailure.set(new SQLException(poolName +" - is at maximum capacity")); + return true; + } + + Connection connection = null; + try { + String username = config.getUsername(); + String password = config.getPassword(); + + connection = (username == null && password == null) ? dataSource.getConnection() : dataSource.getConnection(username, password); + poolElf.setupConnection(connection, connectionTimeout); + + connectionBag.add(new PoolBagEntry(connection, this)); + lastConnectionFailure.set(null); + LOGGER.debug("{} - Added connection {}", poolName, connection); + + return true; + } + catch (Exception e) { + totalConnections.decrementAndGet(); // We failed, so undo speculative increment of totalConnections + lastConnectionFailure.set(e); + if (poolState == POOL_NORMAL) { + LOGGER.debug("{} - Cannot acquire connection from data source", poolName, e); + } + poolElf.quietlyCloseConnection(connection, "(exception during connection creation)"); + return false; + } + } + + /** + * Fill pool up from current idle connections (as they are perceived at the point of execution) to minimumIdle connections. + */ + private void fillPool() + { + final int connectionsToAdd = Math.min(config.getMaximumPoolSize() - totalConnections.get(), config.getMinimumIdle() - getIdleConnections()); + for (int i = 0; i < connectionsToAdd; i++) { + addBagItem(); + } + + if (connectionsToAdd > 0 && LOGGER.isDebugEnabled()) { + addConnectionExecutor.execute(new Runnable() { + @Override + public void run() { + logPoolState("After fill "); + } + }); + } + } + + /** + * Attempt to abort() active connections, or close() them. + */ + private void abortActiveConnections(final ExecutorService assassinExecutor) + { + for (PoolBagEntry bagEntry : connectionBag.values(STATE_IN_USE)) { + try { + bagEntry.aborted = bagEntry.evicted = true; + bagEntry.connection.abort(assassinExecutor); + } + catch (Throwable e) { + poolElf.quietlyCloseConnection(bagEntry.connection, "(connection aborted during shutdown)"); + } + finally { + bagEntry.connection = null; + if (connectionBag.remove(bagEntry)) { + totalConnections.decrementAndGet(); + } + } + } + } + + /** + * Fill the pool up to the minimum size. + */ + private void initializeConnections() + { + if (config.isInitializationFailFast()) { + try { + if (!addConnection()) { + throw lastConnectionFailure.getAndSet(null); + } + + ConnectionProxy connection = (ConnectionProxy) getConnection(); + connection.getPoolBagEntry().evicted = (config.getMinimumIdle() == 0); + connection.close(); + } + catch (Throwable e) { + try { + shutdown(); + } + catch (Throwable ex) { + e.addSuppressed(ex); + } + + throw new PoolInitializationException(e); + } + } + + fillPool(); + } + + private PoolStats getPoolStats() + { + return new PoolStats(TimeUnit.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(); + } + }; + } + + // *********************************************************************** + // Non-anonymous Inner-classes + // *********************************************************************** + + /** + * The house keeping task to retire idle connections. + */ + private class HouseKeeper implements Runnable + { + private volatile long previous = clockSource.currentTime(); + + @Override + public void run() + { + // refresh timeouts in case they changed via MBean + connectionTimeout = config.getConnectionTimeout(); + poolElf.setValidationTimeout(config.getValidationTimeout()); + leakTask.updateLeakDetectionThreshold(config.getLeakDetectionThreshold()); + + final long now = clockSource.currentTime(); + final long idleTimeout = config.getIdleTimeout(); + + // Detect retrograde time as well as forward leaps of unacceptable duration + if (now < previous || now > clockSource.plusMillis(previous, (2 * HOUSEKEEPING_PERIOD_MS))) { + LOGGER.warn("{} - Unusual system clock change detected, soft-evicting connections from pool.", poolName); + softEvictConnections(); + fillPool(); + return; + } + + previous = now; + + logPoolState("Before cleanup "); + for (PoolBagEntry bagEntry : connectionBag.values(STATE_NOT_IN_USE)) { + if (connectionBag.reserve(bagEntry)) { + if (bagEntry.evicted) { + closeConnection(bagEntry, "(connection evicted)"); + } + else if (idleTimeout > 0L && clockSource.elapsedMillis(bagEntry.lastAccess, now) > idleTimeout) { + closeConnection(bagEntry, "(connection passed idleTimeout)"); + } + else { + connectionBag.unreserve(bagEntry); + } + } + } + + logPoolState("After cleanup "); + + fillPool(); // Try to maintain minimum connections + } + } +} diff --git a/src/main/java/com/zaxxer/hikari/pool/PoolElf.java b/src/main/java/com/zaxxer/hikari/pool/PoolElf.java index a530e978..b0a2e6f7 100644 --- a/src/main/java/com/zaxxer/hikari/pool/PoolElf.java +++ b/src/main/java/com/zaxxer/hikari/pool/PoolElf.java @@ -1,524 +1,520 @@ -package com.zaxxer.hikari.pool; - -import static com.zaxxer.hikari.util.UtilityElf.createInstance; - -import java.lang.management.ManagementFactory; -import java.lang.reflect.Field; -import java.sql.Connection; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.Properties; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import javax.management.MBeanServer; -import javax.management.ObjectName; -import javax.sql.DataSource; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.util.DefaultThreadFactory; -import com.zaxxer.hikari.util.DriverDataSource; -import com.zaxxer.hikari.util.PropertyElf; - -public final class PoolElf -{ - private static final Logger LOGGER = LoggerFactory.getLogger(PoolElf.class); - private static final String[] RESET_STATES = {"readOnly", "autoCommit", "isolation", "catalog", "netTimeout"}; - private static final int UNINITIALIZED = -1; - private static final int TRUE = 1; - private static final int FALSE = 0; - - private int networkTimeout; - private int transactionIsolation; - private long validationTimeout; - private int isNetworkTimeoutSupported; - private int isQueryTimeoutSupported; - private Executor netTimeoutExecutor; - - private final HikariConfig config; - private final String poolName; - private final String catalog; - private final Boolean isReadOnly; - private final boolean isAutoCommit; - private final boolean isUseJdbc4Validation; - private final boolean isIsolateInternalQueries; - - private volatile boolean isValidChecked; - private volatile boolean isValidSupported; - - public PoolElf(final HikariConfig configuration) - { - this.config = configuration; - - this.networkTimeout = -1; - this.catalog = config.getCatalog(); - this.isReadOnly = config.isReadOnly(); - this.isAutoCommit = config.isAutoCommit(); - this.validationTimeout = config.getValidationTimeout(); - this.transactionIsolation = getTransactionIsolation(config.getTransactionIsolation()); - - this.isValidSupported = true; - this.isQueryTimeoutSupported = UNINITIALIZED; - this.isNetworkTimeoutSupported = UNINITIALIZED; - this.isUseJdbc4Validation = config.getConnectionTestQuery() == null; - this.isIsolateInternalQueries = config.isIsolateInternalQueries(); - this.poolName = config.getPoolName(); - } - - /** - * Close connection and eat any exception. - * - * @param connection the connection to close - * @param closureReason the reason the connection was closed (if known) - */ - public void quietlyCloseConnection(final Connection connection, final String closureReason) - { - try { - if (connection == null || connection.isClosed()) { - return; - } - - LOGGER.debug("{} - Closing connection {}: {}", poolName, connection, closureReason); - try { - setNetworkTimeout(connection, TimeUnit.SECONDS.toMillis(15)); - } - finally { - // continue with the close even if setNetworkTimeout() throws (due to driver poorly behaving drivers) - connection.close(); - } - } - catch (Throwable e) { - LOGGER.debug("{} - Closing connection {} failed", poolName, connection, e); - } - } - - /** - * 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 { - Field field = Connection.class.getField(transactionIsolationName); - return field.getInt(null); - } - catch (Exception e) { - throw new IllegalArgumentException("Invalid transaction isolation value: " + transactionIsolationName); - } - } - - return -1; - } - - /** - * Create/initialize the underlying DataSource. - * - * @return a DataSource instance - */ - DataSource initializeDataSource() - { - final String jdbcUrl = config.getJdbcUrl(); - final String username = config.getUsername(); - final String password = config.getPassword(); - final String dsClassName = config.getDataSourceClassName(); - final String driverClassName = config.getDriverClassName(); - final Properties dataSourceProperties = config.getDataSourceProperties(); - - DataSource dataSource = config.getDataSource(); - if (dsClassName != null && dataSource == null) { - dataSource = createInstance(dsClassName, DataSource.class); - PropertyElf.setTargetFromProperties(dataSource, dataSourceProperties); - } - else if (jdbcUrl != null && dataSource == null) { - dataSource = new DriverDataSource(jdbcUrl, driverClassName, dataSourceProperties, username, password); - } - - if (dataSource != null) { - setLoginTimeout(dataSource, config.getConnectionTimeout()); - createNetworkTimeoutExecutor(dataSource, dsClassName, jdbcUrl); - } - - return dataSource; - } - - /** - * Setup a connection initial state. - * - * @param connection a Connection - * @param connectionTimeout the connection timeout - * @throws SQLException thrown from driver - */ - void setupConnection(final Connection connection, final long connectionTimeout) throws SQLException - { - if (isUseJdbc4Validation && !isJdbc4ValidationSupported(connection)) { - throw new SQLException("Connection.isValid() method is not supported, connection test query must be configured"); - } - - networkTimeout = getAndSetNetworkTimeout(connection, connectionTimeout); - transactionIsolation = (transactionIsolation < 0 ? connection.getTransactionIsolation() : transactionIsolation); - - connection.setAutoCommit(isAutoCommit); - if (isReadOnly != null) { - connection.setReadOnly(isReadOnly); - } - - if (transactionIsolation != connection.getTransactionIsolation()) { - connection.setTransactionIsolation(transactionIsolation); - } - - if (catalog != null) { - connection.setCatalog(catalog); - } - - executeSql(connection, config.getConnectionInitSql(), isAutoCommit); - - setNetworkTimeout(connection, networkTimeout); - } - - /** - * Check whether the connection is alive or not. - * - * @param connection the connection to test - * @param lastConnectionFailure last connection failure - * @return true if the connection is alive, false if it is not alive or we timed out - */ - boolean isConnectionAlive(final Connection connection, AtomicReference lastConnectionFailure) - { - try { - int timeoutSec = (int) TimeUnit.MILLISECONDS.toSeconds(validationTimeout); - - if (isUseJdbc4Validation) { - return connection.isValid(timeoutSec); - } - - final int originalTimeout = getAndSetNetworkTimeout(connection, validationTimeout); - - try (Statement statement = connection.createStatement()) { - setQueryTimeout(statement, timeoutSec); - if (statement.execute(config.getConnectionTestQuery())) { - statement.getResultSet().close(); - } - } - - if (isIsolateInternalQueries && !isAutoCommit) { - connection.rollback(); - } - - setNetworkTimeout(connection, originalTimeout); - - return true; - } - catch (SQLException e) { - lastConnectionFailure.set(e); - LOGGER.warn("{} - Connection {} failed alive test with exception {}", poolName, connection, e.getMessage()); - return false; - } - } - - void resetConnectionState(final PoolBagEntry poolEntry) throws SQLException - { - int resetBits = 0; - - if (isReadOnly != null && poolEntry.isReadOnly != isReadOnly) { - poolEntry.connection.setReadOnly(isReadOnly); - resetBits |= 0b00001; - } - - if (poolEntry.isAutoCommit != isAutoCommit) { - poolEntry.connection.setAutoCommit(isAutoCommit); - resetBits |= 0b00010; - } - - if (poolEntry.transactionIsolation != transactionIsolation) { - poolEntry.connection.setTransactionIsolation(transactionIsolation); - resetBits |= 0b00100; - } - - final String currentCatalog = poolEntry.catalog; - if ((currentCatalog != null && !currentCatalog.equals(catalog)) || (currentCatalog == null && catalog != null)) { - poolEntry.connection.setCatalog(catalog); - resetBits |= 0b01000; - } - - if (poolEntry.networkTimeout != networkTimeout) { - setNetworkTimeout(poolEntry.connection, networkTimeout); - resetBits |= 0b10000; - } - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("{} - Reset {} on connection {}", resetBits != 0 ? stringFromResetBits(resetBits) : "nothing", poolEntry.connection); - } - } - - void resetPoolEntry(final PoolBagEntry poolEntry) - { - if (isReadOnly != null) { - poolEntry.setReadOnly(isReadOnly); - } - poolEntry.setCatalog(catalog); - poolEntry.setAutoCommit(isAutoCommit); - poolEntry.setNetworkTimeout(networkTimeout); - poolEntry.setTransactionIsolation(transactionIsolation); - } - - void setValidationTimeout(final long validationTimeout) - { - this.validationTimeout = validationTimeout; - } - - /** - * Register MBeans for HikariConfig and HikariPool. - * - * @param pool a HikariPool instance - */ - void registerMBeans(final HikariPool pool) - { - if (!config.isRegisterMbeans()) { - return; - } - - try { - final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); - - final ObjectName beanConfigName = new ObjectName("com.zaxxer.hikari:type=PoolConfig (" + poolName + ")"); - final ObjectName beanPoolName = new ObjectName("com.zaxxer.hikari:type=Pool (" + poolName + ")"); - if (!mBeanServer.isRegistered(beanConfigName)) { - mBeanServer.registerMBean(config, beanConfigName); - mBeanServer.registerMBean(pool, beanPoolName); - } - else { - LOGGER.error("{} - You cannot use the same pool name for separate pool instances.", poolName); - } - } - catch (Exception e) { - LOGGER.warn("{} - Unable to register management beans.", poolName, e); - } - } - - /** - * Unregister MBeans for HikariConfig and HikariPool. - */ - void unregisterMBeans() - { - if (!config.isRegisterMbeans()) { - return; - } - - try { - final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); - - final ObjectName beanConfigName = new ObjectName("com.zaxxer.hikari:type=PoolConfig (" + poolName + ")"); - final ObjectName beanPoolName = new ObjectName("com.zaxxer.hikari:type=Pool (" + poolName + ")"); - if (mBeanServer.isRegistered(beanConfigName)) { - mBeanServer.unregisterMBean(beanConfigName); - mBeanServer.unregisterMBean(beanPoolName); - } - } - catch (Exception e) { - LOGGER.warn("{} - Unable to unregister management beans.", poolName, e); - } - } - - void shutdownTimeoutExecutor() - { - if (netTimeoutExecutor != null && netTimeoutExecutor instanceof ThreadPoolExecutor) { - ((ThreadPoolExecutor) netTimeoutExecutor).shutdownNow(); - } - } - - /** - * Return true if the driver appears to be JDBC 4.0 compliant. - * - * @param connection a Connection to check - * @return true if JDBC 4.1 compliance, false otherwise - */ - private boolean isJdbc4ValidationSupported(final Connection connection) - { - if (!isValidChecked) { - try { - // We don't care how long the wait actually is here, just whether it returns without exception. This - // call will throw various exceptions in the case of a non-JDBC 4.0 compliant driver - connection.isValid(1); - } - catch (Throwable e) { - isValidSupported = false; - LOGGER.debug("{} - Connection.isValid() is not supported ({})", poolName, e.getMessage()); - } - - isValidChecked = true; - } - - return isValidSupported; - } - - /** - * 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 (Throwable e) { - if (isQueryTimeoutSupported == UNINITIALIZED) { - isQueryTimeoutSupported = FALSE; - LOGGER.debug("{} - Statement.setQueryTimeout() is not supported ({})", 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 int originalTimeout = connection.getNetworkTimeout(); - connection.setNetworkTimeout(netTimeoutExecutor, (int) timeoutMs); - isNetworkTimeoutSupported = TRUE; - return originalTimeout; - } - catch (Throwable e) { - if (isNetworkTimeoutSupported == UNINITIALIZED) { - isNetworkTimeoutSupported = FALSE; - LOGGER.debug("{} - Connection.setNetworkTimeout() is not supported ({})", poolName, e.getMessage()); - } - } - } - - 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 isAutoCommit 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 isAutoCommit) throws SQLException - { - if (sql != null) { - try (Statement statement = connection.createStatement()) { - if (statement.execute(sql)) { - statement.getResultSet().close(); - } - - if (!isAutoCommit) { - connection.commit(); - } - } - } - } - - 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() != null ? config.getThreadFactory() : new DefaultThreadFactory("Hikari JDBC-timeout executor", true); - ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool(threadFactory); - executor.allowCoreThreadTimeOut(true); - executor.setKeepAliveTime(15, TimeUnit.SECONDS); - netTimeoutExecutor = executor; - } - } - - private static class SynchronousExecutor implements Executor - { - /** {@inheritDoc} */ - @Override - public void execute(Runnable command) - { - try { - command.run(); - } - catch (Throwable t) { - LOGGER.debug("Exception executing {}", command, t); - } - } - } - - /** - * Set the loginTimeout on the specified DataSource. - * - * @param dataSource the DataSource - * @param connectionTimeout the timeout in milliseconds - */ - private void setLoginTimeout(final DataSource dataSource, final long connectionTimeout) - { - if (connectionTimeout != Integer.MAX_VALUE) { - try { - dataSource.setLoginTimeout((int) TimeUnit.MILLISECONDS.toSeconds(Math.max(1000L, connectionTimeout))); - } - catch (SQLException e) { - LOGGER.warn("{} - Unable to set DataSource login timeout", poolName, e); - } - } - } - - /** - * This will create a string for debug logging. Given a set of "reset bits", this - * method will return a concatenated string, for example: - * - * Input : 0b00110 - * Output: "autoCommit, isolation" - * - * @param bits a set of "reset bits" - * @return a string of which states were reset - */ - private String stringFromResetBits(final int bits) - { - final StringBuilder sb = new StringBuilder(); - for (int ndx = 0; ndx < RESET_STATES.length; ndx++) { - if ( (bits & (0b1 << ndx)) != 0) { - sb.append(RESET_STATES[ndx]).append(", "); - } - } - - sb.setLength(sb.length() - 2); // trim trailing comma - return sb.toString(); - } -} +package com.zaxxer.hikari.pool; + +import static com.zaxxer.hikari.util.UtilityElf.createInstance; + +import java.lang.management.ManagementFactory; +import java.lang.reflect.Field; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import javax.management.MBeanServer; +import javax.management.ObjectName; +import javax.sql.DataSource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.util.DefaultThreadFactory; +import com.zaxxer.hikari.util.DriverDataSource; +import com.zaxxer.hikari.util.PropertyElf; + +public final class PoolElf +{ + private static final Logger LOGGER = LoggerFactory.getLogger(PoolElf.class); + private static final String[] RESET_STATES = {"readOnly", "autoCommit", "isolation", "catalog", "netTimeout"}; + private static final int UNINITIALIZED = -1; + private static final int TRUE = 1; + private static final int FALSE = 0; + + private int networkTimeout; + private int transactionIsolation; + private long validationTimeout; + private int isNetworkTimeoutSupported; + private int isQueryTimeoutSupported; + private Executor netTimeoutExecutor; + + private final HikariConfig config; + private final String poolName; + private final String catalog; + private final Boolean isReadOnly; + private final boolean isAutoCommit; + private final boolean isUseJdbc4Validation; + private final boolean isIsolateInternalQueries; + + private volatile boolean isValidChecked; + private volatile boolean isValidSupported; + + public PoolElf(final HikariConfig configuration) + { + this.config = configuration; + + this.networkTimeout = -1; + this.catalog = config.getCatalog(); + this.isReadOnly = config.isReadOnly(); + this.isAutoCommit = config.isAutoCommit(); + this.validationTimeout = config.getValidationTimeout(); + this.transactionIsolation = getTransactionIsolation(config.getTransactionIsolation()); + + this.isValidSupported = true; + this.isQueryTimeoutSupported = UNINITIALIZED; + this.isNetworkTimeoutSupported = UNINITIALIZED; + this.isUseJdbc4Validation = config.getConnectionTestQuery() == null; + this.isIsolateInternalQueries = config.isIsolateInternalQueries(); + this.poolName = config.getPoolName(); + } + + /** + * Close connection and eat any exception. + * + * @param connection the connection to close + * @param closureReason the reason the connection was closed (if known) + */ + public void quietlyCloseConnection(final Connection connection, final String closureReason) + { + try { + if (connection == null || connection.isClosed()) { + return; + } + + LOGGER.debug("{} - Closing connection {}: {}", poolName, connection, closureReason); + try { + setNetworkTimeout(connection, TimeUnit.SECONDS.toMillis(15)); + } + finally { + // continue with the close even if setNetworkTimeout() throws (due to driver poorly behaving drivers) + connection.close(); + } + } + catch (Throwable e) { + LOGGER.debug("{} - Closing connection {} failed", poolName, connection, e); + } + } + + /** + * 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 { + Field field = Connection.class.getField(transactionIsolationName); + return field.getInt(null); + } + catch (Exception e) { + throw new IllegalArgumentException(poolName + " - Invalid transaction isolation value: " + transactionIsolationName); + } + } + + return -1; + } + + /** + * Create/initialize the underlying DataSource. + * + * @return a DataSource instance + */ + DataSource initializeDataSource() + { + final String jdbcUrl = config.getJdbcUrl(); + final String username = config.getUsername(); + final String password = config.getPassword(); + final String dsClassName = config.getDataSourceClassName(); + final String driverClassName = config.getDriverClassName(); + final Properties dataSourceProperties = config.getDataSourceProperties(); + + DataSource dataSource = config.getDataSource(); + if (dsClassName != null && dataSource == null) { + dataSource = createInstance(dsClassName, DataSource.class); + PropertyElf.setTargetFromProperties(dataSource, dataSourceProperties); + } + else if (jdbcUrl != null && dataSource == null) { + dataSource = new DriverDataSource(jdbcUrl, driverClassName, dataSourceProperties, username, password); + } + + if (dataSource != null) { + setLoginTimeout(dataSource, config.getConnectionTimeout()); + createNetworkTimeoutExecutor(dataSource, dsClassName, jdbcUrl); + } + + return dataSource; + } + + /** + * Setup a connection initial state. + * + * @param connection a Connection + * @param connectionTimeout the connection timeout + * @throws SQLException thrown from driver + */ + void setupConnection(final Connection connection, final long connectionTimeout) throws SQLException + { + if (isUseJdbc4Validation && !isJdbc4ValidationSupported(connection)) { + throw new SQLException("Connection.isValid() is not supported, connection test query must be configured"); + } + + networkTimeout = getAndSetNetworkTimeout(connection, connectionTimeout); + transactionIsolation = (transactionIsolation < 0 ? connection.getTransactionIsolation() : transactionIsolation); + + connection.setAutoCommit(isAutoCommit); + if (isReadOnly != null) { + connection.setReadOnly(isReadOnly); + } + + if (transactionIsolation != connection.getTransactionIsolation()) { + connection.setTransactionIsolation(transactionIsolation); + } + + if (catalog != null) { + connection.setCatalog(catalog); + } + + executeSql(connection, config.getConnectionInitSql(), isAutoCommit); + + setNetworkTimeout(connection, networkTimeout); + } + + /** + * Check whether the connection is alive or not. + * + * @param connection the connection to test + * @param lastConnectionFailure last connection failure + * @return true if the connection is alive, false if it is not alive or we timed out + */ + boolean isConnectionAlive(final Connection connection, AtomicReference lastConnectionFailure) + { + try { + int timeoutSec = (int) TimeUnit.MILLISECONDS.toSeconds(validationTimeout); + + if (isUseJdbc4Validation) { + return connection.isValid(timeoutSec); + } + + final int originalTimeout = getAndSetNetworkTimeout(connection, validationTimeout); + + try (Statement statement = connection.createStatement()) { + setQueryTimeout(statement, timeoutSec); + statement.execute(config.getConnectionTestQuery()); + } + + if (isIsolateInternalQueries && !isAutoCommit) { + connection.rollback(); + } + + setNetworkTimeout(connection, originalTimeout); + + return true; + } + catch (SQLException e) { + lastConnectionFailure.set(e); + LOGGER.warn("{} - Connection {} failed alive test with exception {}", poolName, connection, e.getMessage()); + return false; + } + } + + void resetConnectionState(final PoolBagEntry poolEntry) throws SQLException + { + int resetBits = 0; + + if (isReadOnly != null && poolEntry.isReadOnly != isReadOnly) { + poolEntry.connection.setReadOnly(isReadOnly); + resetBits |= 0b00001; + } + + if (poolEntry.isAutoCommit != isAutoCommit) { + poolEntry.connection.setAutoCommit(isAutoCommit); + resetBits |= 0b00010; + } + + if (poolEntry.transactionIsolation != transactionIsolation) { + poolEntry.connection.setTransactionIsolation(transactionIsolation); + resetBits |= 0b00100; + } + + final String currentCatalog = poolEntry.catalog; + if ((currentCatalog != null && !currentCatalog.equals(catalog)) || (currentCatalog == null && catalog != null)) { + poolEntry.connection.setCatalog(catalog); + resetBits |= 0b01000; + } + + if (poolEntry.networkTimeout != networkTimeout) { + setNetworkTimeout(poolEntry.connection, networkTimeout); + resetBits |= 0b10000; + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} - Reset {} on connection {}", resetBits != 0 ? stringFromResetBits(resetBits) : "nothing", poolEntry.connection); + } + } + + void resetPoolEntry(final PoolBagEntry poolEntry) + { + if (isReadOnly != null) { + poolEntry.setReadOnly(isReadOnly); + } + poolEntry.setCatalog(catalog); + poolEntry.setAutoCommit(isAutoCommit); + poolEntry.setNetworkTimeout(networkTimeout); + poolEntry.setTransactionIsolation(transactionIsolation); + } + + void setValidationTimeout(final long validationTimeout) + { + this.validationTimeout = validationTimeout; + } + + /** + * Register MBeans for HikariConfig and HikariPool. + * + * @param pool a HikariPool instance + */ + void registerMBeans(final HikariPool pool) + { + if (!config.isRegisterMbeans()) { + return; + } + + try { + final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); + + final ObjectName beanConfigName = new ObjectName("com.zaxxer.hikari:type=PoolConfig (" + poolName + ")"); + final ObjectName beanPoolName = new ObjectName("com.zaxxer.hikari:type=Pool (" + poolName + ")"); + if (!mBeanServer.isRegistered(beanConfigName)) { + mBeanServer.registerMBean(config, beanConfigName); + mBeanServer.registerMBean(pool, beanPoolName); + } + else { + LOGGER.error("{} - You cannot use the same pool name for separate pool instances.", poolName); + } + } + catch (Exception e) { + LOGGER.warn("{} - Unable to register management beans.", poolName, e); + } + } + + /** + * Unregister MBeans for HikariConfig and HikariPool. + */ + void unregisterMBeans() + { + if (!config.isRegisterMbeans()) { + return; + } + + try { + final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); + + final ObjectName beanConfigName = new ObjectName("com.zaxxer.hikari:type=PoolConfig (" + poolName + ")"); + final ObjectName beanPoolName = new ObjectName("com.zaxxer.hikari:type=Pool (" + poolName + ")"); + if (mBeanServer.isRegistered(beanConfigName)) { + mBeanServer.unregisterMBean(beanConfigName); + mBeanServer.unregisterMBean(beanPoolName); + } + } + catch (Exception e) { + LOGGER.warn("{} - Unable to unregister management beans.", poolName, e); + } + } + + void shutdownTimeoutExecutor() + { + if (netTimeoutExecutor != null && netTimeoutExecutor instanceof ThreadPoolExecutor) { + ((ThreadPoolExecutor) netTimeoutExecutor).shutdownNow(); + } + } + + /** + * Return true if the driver appears to be JDBC 4.0 compliant. + * + * @param connection a Connection to check + * @return true if JDBC 4.1 compliance, false otherwise + */ + private boolean isJdbc4ValidationSupported(final Connection connection) + { + if (!isValidChecked) { + try { + // We don't care how long the wait actually is here, just whether it returns without exception. This + // call will throw various exceptions in the case of a non-JDBC 4.0 compliant driver + connection.isValid(1); + } + catch (Throwable e) { + isValidSupported = false; + LOGGER.debug("{} - Connection.isValid() is not supported ({})", poolName, e.getMessage()); + } + + isValidChecked = true; + } + + return isValidSupported; + } + + /** + * 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 (Throwable e) { + if (isQueryTimeoutSupported == UNINITIALIZED) { + isQueryTimeoutSupported = FALSE; + LOGGER.debug("{} - Statement.setQueryTimeout() is not supported ({})", 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 int originalTimeout = connection.getNetworkTimeout(); + connection.setNetworkTimeout(netTimeoutExecutor, (int) timeoutMs); + isNetworkTimeoutSupported = TRUE; + return originalTimeout; + } + catch (Throwable e) { + if (isNetworkTimeoutSupported == UNINITIALIZED) { + isNetworkTimeoutSupported = FALSE; + LOGGER.debug("{} - Connection.setNetworkTimeout() is not supported ({})", poolName, e.getMessage()); + } + } + } + + 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 isAutoCommit 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 isAutoCommit) throws SQLException + { + if (sql != null) { + try (Statement statement = connection.createStatement()) { + statement.execute(sql); + + if (!isAutoCommit) { + connection.commit(); + } + } + } + } + + 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() != null ? config.getThreadFactory() : new DefaultThreadFactory("Hikari JDBC-timeout executor", true); + ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool(threadFactory); + executor.allowCoreThreadTimeOut(true); + executor.setKeepAliveTime(15, TimeUnit.SECONDS); + netTimeoutExecutor = executor; + } + } + + private static class SynchronousExecutor implements Executor + { + /** {@inheritDoc} */ + @Override + public void execute(Runnable command) + { + try { + command.run(); + } + catch (Throwable t) { + LOGGER.debug("Exception executing {}", command, t); + } + } + } + + /** + * Set the loginTimeout on the specified DataSource. + * + * @param dataSource the DataSource + * @param connectionTimeout the timeout in milliseconds + */ + private void setLoginTimeout(final DataSource dataSource, final long connectionTimeout) + { + if (connectionTimeout != Integer.MAX_VALUE) { + try { + dataSource.setLoginTimeout((int) TimeUnit.MILLISECONDS.toSeconds(Math.max(1000L, connectionTimeout))); + } + catch (SQLException e) { + LOGGER.warn("{} - Unable to set DataSource login timeout", poolName, e); + } + } + } + + /** + * This will create a string for debug logging. Given a set of "reset bits", this + * method will return a concatenated string, for example: + * + * Input : 0b00110 + * Output: "autoCommit, isolation" + * + * @param bits a set of "reset bits" + * @return a string of which states were reset + */ + private String stringFromResetBits(final int bits) + { + final StringBuilder sb = new StringBuilder(); + for (int ndx = 0; ndx < RESET_STATES.length; ndx++) { + if ( (bits & (0b1 << ndx)) != 0) { + sb.append(RESET_STATES[ndx]).append(", "); + } + } + + sb.setLength(sb.length() - 2); // trim trailing comma + return sb.toString(); + } +}