From 8fd73d250191b6e75e992b6b7bcb407d9eeeea06 Mon Sep 17 00:00:00 2001 From: Nitin Date: Mon, 20 Jul 2015 20:54:14 +0530 Subject: [PATCH 01/14] 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(); + } +} From 7bb009be247ca708669e03ca30629b79d508fa35 Mon Sep 17 00:00:00 2001 From: Nitin Date: Mon, 20 Jul 2015 21:42:50 +0530 Subject: [PATCH 02/14] 2nd try --- .../com/zaxxer/hikari/pool/HikariPool.java | 1304 ++++++++--------- .../java/com/zaxxer/hikari/pool/PoolElf.java | 1040 ++++++------- 2 files changed, 1172 insertions(+), 1172 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/pool/HikariPool.java b/src/main/java/com/zaxxer/hikari/pool/HikariPool.java index 1897d7ec..64a70703 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(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 - } - } -} +/* + * 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 shutting 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 b0a2e6f7..a94b4516 100644 --- a/src/main/java/com/zaxxer/hikari/pool/PoolElf.java +++ b/src/main/java/com/zaxxer/hikari/pool/PoolElf.java @@ -1,520 +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(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(); - } -} +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); + 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(); + } +} From e313835b94627e7d448ba27e41dcb3ab9f2bb533 Mon Sep 17 00:00:00 2001 From: Nitin Date: Tue, 21 Jul 2015 11:58:32 +0530 Subject: [PATCH 03/14] changes as suggested --- src/main/java/com/zaxxer/hikari/pool/HikariPool.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/pool/HikariPool.java b/src/main/java/com/zaxxer/hikari/pool/HikariPool.java index 64a70703..ac365afb 100644 --- a/src/main/java/com/zaxxer/hikari/pool/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/pool/HikariPool.java @@ -24,7 +24,7 @@ import static com.zaxxer.hikari.util.UtilityElf.quietlySleep; import java.sql.Connection; import java.sql.SQLException; -import java.sql.SQLTimeoutException; +import java.sql.SQLTransientConnectionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; @@ -208,7 +208,7 @@ public class HikariPool implements HikariPoolMXBean, IBagStateListener if (originalException instanceof SQLException) { sqlState = ((SQLException) originalException).getSQLState(); } - throw new SQLTimeoutException(poolName + " - Cannot acquire connection, Timeout after " + clockSource.elapsedMillis(startTime) + "ms.", sqlState, originalException); + throw new SQLTransientConnectionException(poolName + " - Connection not available, timeout after " + clockSource.elapsedMillis(startTime) + "ms.", sqlState, originalException); } /** From ee043a30312ae3b96864827a4746eee3fd56fd49 Mon Sep 17 00:00:00 2001 From: Nitin Date: Tue, 21 Jul 2015 13:47:05 +0530 Subject: [PATCH 04/14] better --- src/main/java/com/zaxxer/hikari/pool/HikariPool.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/zaxxer/hikari/pool/HikariPool.java b/src/main/java/com/zaxxer/hikari/pool/HikariPool.java index ac365afb..04b49159 100644 --- a/src/main/java/com/zaxxer/hikari/pool/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/pool/HikariPool.java @@ -208,7 +208,7 @@ public class HikariPool implements HikariPoolMXBean, IBagStateListener if (originalException instanceof SQLException) { sqlState = ((SQLException) originalException).getSQLState(); } - throw new SQLTransientConnectionException(poolName + " - Connection not available, timeout after " + clockSource.elapsedMillis(startTime) + "ms.", sqlState, originalException); + throw new SQLTransientConnectionException(poolName + " - Connection is not available, request timed out after " + clockSource.elapsedMillis(startTime) + "ms.", sqlState, originalException); } /** From c406837755d1c717e24846d1c8fd4aa6cb625f1f Mon Sep 17 00:00:00 2001 From: Nitin Date: Tue, 21 Jul 2015 16:03:57 +0530 Subject: [PATCH 05/14] few more --- src/main/java/com/zaxxer/hikari/HikariConfig.java | 9 ++++----- src/main/java/com/zaxxer/hikari/HikariDataSource.java | 8 ++++---- src/main/java/com/zaxxer/hikari/pool/HikariPool.java | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/HikariConfig.java b/src/main/java/com/zaxxer/hikari/HikariConfig.java index f2bea8d1..d564cb77 100644 --- a/src/main/java/com/zaxxer/hikari/HikariConfig.java +++ b/src/main/java/com/zaxxer/hikari/HikariConfig.java @@ -452,7 +452,7 @@ public class HikariConfig implements HikariConfigMXBean public void setMetricsTrackerFactory(MetricsTrackerFactory metricsTrackerFactory) { if (metricRegistry != null) { - throw new IllegalStateException("setMetricsTrackerFactory() cannot used in combination with setMetricRegistry()"); + throw new IllegalStateException("cannot use setMetricsTrackerFactory() and setMetricRegistry() together"); } this.metricsTrackerFactory = metricsTrackerFactory; @@ -474,9 +474,8 @@ public class HikariConfig implements HikariConfigMXBean * @param metricRegistry the Codahale MetricRegistry to set */ public void setMetricRegistry(Object metricRegistry) - { if (metricsTrackerFactory != null) { - throw new IllegalStateException("setMetricRegistry() cannot used in combination with setMetricsTrackerFactory()"); + throw new IllegalStateException("cannot use setMetricRegistry() and setMetricsTrackerFactory() together"); } if (metricRegistry != null) { @@ -760,11 +759,11 @@ public class HikariConfig implements HikariConfigMXBean if (driverClassName != null && jdbcUrl == null) { logger.error("jdbcUrl is required with driverClassName"); - throw new IllegalStateException("jdbcUrl is required with driverClassName"); + throw new IllegalArgumentException("jdbcUrl is required with driverClassName"); } else if (driverClassName != null && dataSourceClassName != null) { logger.error("cannot use driverClassName and dataSourceClassName together"); - throw new IllegalStateException("cannot use driverClassName and dataSourceClassName together"); + throw new IllegalArgumentException("cannot use driverClassName and dataSourceClassName together"); } else if (jdbcUrl != null) { // OK diff --git a/src/main/java/com/zaxxer/hikari/HikariDataSource.java b/src/main/java/com/zaxxer/hikari/HikariDataSource.java index fc125eb9..985c1cb2 100644 --- a/src/main/java/com/zaxxer/hikari/HikariDataSource.java +++ b/src/main/java/com/zaxxer/hikari/HikariDataSource.java @@ -69,7 +69,7 @@ public class HikariDataSource extends HikariConfig implements DataSource, Closea configuration.validate(); configuration.copyState(this); - LOGGER.info("Hikari pool {} is starting.", configuration.getPoolName()); + LOGGER.info("{} - is starting.", configuration.getPoolName()); pool = fastPathPool = new HikariPool(this); } @@ -78,7 +78,7 @@ public class HikariDataSource extends HikariConfig implements DataSource, Closea public Connection getConnection() throws SQLException { if (isClosed()) { - throw new SQLException("HikariDataSource " + this + " has been shutdown"); + throw new SQLException("HikariDataSource " + this + " has been closed."); } if (fastPathPool != null) { @@ -92,7 +92,7 @@ public class HikariDataSource extends HikariConfig implements DataSource, Closea result = pool; if (result == null) { validate(); - LOGGER.info("Hikari pool {} is starting.", getPoolName()); + LOGGER.info("{} - is starting.", getPoolName()); pool = result = new HikariPool(this); } } @@ -289,7 +289,7 @@ public class HikariDataSource extends HikariConfig implements DataSource, Closea pool.shutdown(); } catch (InterruptedException e) { - LoggerFactory.getLogger(getClass()).warn("Interrupted during shutdown", e); + LOGGER.warn("Interrupted during closing", e); } if (pool.getDataSource() instanceof DriverDataSource) { diff --git a/src/main/java/com/zaxxer/hikari/pool/HikariPool.java b/src/main/java/com/zaxxer/hikari/pool/HikariPool.java index 04b49159..7b710cbe 100644 --- a/src/main/java/com/zaxxer/hikari/pool/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/pool/HikariPool.java @@ -239,7 +239,7 @@ public class HikariPool implements HikariPoolMXBean, IBagStateListener try { poolState = POOL_SHUTDOWN; - LOGGER.info("{} - is shutting down.", poolName); + LOGGER.info("{} - is closing down.", poolName); logPoolState("Before closing "); connectionBag.close(); From 285bcfc415d5ae1ed6f02f47c42d39b865ab96fb Mon Sep 17 00:00:00 2001 From: Nitin Date: Tue, 21 Jul 2015 21:12:14 +0530 Subject: [PATCH 06/14] suggestion to fix issue 349 --- src/main/java/com/zaxxer/hikari/pool/PoolElf.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/com/zaxxer/hikari/pool/PoolElf.java b/src/main/java/com/zaxxer/hikari/pool/PoolElf.java index a94b4516..5273e2d2 100644 --- a/src/main/java/com/zaxxer/hikari/pool/PoolElf.java +++ b/src/main/java/com/zaxxer/hikari/pool/PoolElf.java @@ -109,6 +109,21 @@ public final class PoolElf { if (transactionIsolationName != null) { try { + int level = Integer.parseInt(transactionIsolationName); + //its number + switch (level) { + case Connection.TRANSACTION_READ_UNCOMMITTED: + case Connection.TRANSACTION_READ_COMMITTED: + case Connection.TRANSACTION_REPEATABLE_READ: + case Connection.TRANSACTION_SERIALIZABLE: + return level; + } + throw new IllegalArgumentException("Invalid transaction isolation value: " + transactionIsolationName); + } + catch (Exception e) { + //its name + } + try { Field field = Connection.class.getField(transactionIsolationName); return field.getInt(null); } From 07ca37e7a4c2619067e989d8bf4e6f330f4288aa Mon Sep 17 00:00:00 2001 From: Nitin Date: Tue, 21 Jul 2015 21:21:48 +0530 Subject: [PATCH 07/14] minor save --- src/main/java/com/zaxxer/hikari/pool/PoolElf.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/pool/PoolElf.java b/src/main/java/com/zaxxer/hikari/pool/PoolElf.java index 5273e2d2..d5e241ef 100644 --- a/src/main/java/com/zaxxer/hikari/pool/PoolElf.java +++ b/src/main/java/com/zaxxer/hikari/pool/PoolElf.java @@ -180,14 +180,15 @@ public final class PoolElf } networkTimeout = getAndSetNetworkTimeout(connection, connectionTimeout); - transactionIsolation = (transactionIsolation < 0 ? connection.getTransactionIsolation() : transactionIsolation); connection.setAutoCommit(isAutoCommit); if (isReadOnly != null) { connection.setReadOnly(isReadOnly); } - if (transactionIsolation != connection.getTransactionIsolation()) { + int defaultLevel = connection.getTransactionIsolation(); + transactionIsolation = (transactionIsolation < 0 ? defaultLevel : transactionIsolation); + if (transactionIsolation != defaultLevel) { connection.setTransactionIsolation(transactionIsolation); } From dc5b484f52006180823840364f6770f73a80d566 Mon Sep 17 00:00:00 2001 From: Nitin Date: Wed, 22 Jul 2015 12:16:37 +0530 Subject: [PATCH 08/14] better fix --- .../java/com/zaxxer/hikari/HikariConfig.java | 1 + .../java/com/zaxxer/hikari/pool/PoolElf.java | 35 ++++++++++--------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/HikariConfig.java b/src/main/java/com/zaxxer/hikari/HikariConfig.java index d564cb77..665800d0 100644 --- a/src/main/java/com/zaxxer/hikari/HikariConfig.java +++ b/src/main/java/com/zaxxer/hikari/HikariConfig.java @@ -474,6 +474,7 @@ public class HikariConfig implements HikariConfigMXBean * @param metricRegistry the Codahale MetricRegistry to set */ public void setMetricRegistry(Object metricRegistry) + { if (metricsTrackerFactory != null) { throw new IllegalStateException("cannot use setMetricRegistry() and setMetricsTrackerFactory() together"); } diff --git a/src/main/java/com/zaxxer/hikari/pool/PoolElf.java b/src/main/java/com/zaxxer/hikari/pool/PoolElf.java index d5e241ef..cc5158a0 100644 --- a/src/main/java/com/zaxxer/hikari/pool/PoolElf.java +++ b/src/main/java/com/zaxxer/hikari/pool/PoolElf.java @@ -109,23 +109,24 @@ public final class PoolElf { if (transactionIsolationName != null) { try { - int level = Integer.parseInt(transactionIsolationName); - //its number - switch (level) { - case Connection.TRANSACTION_READ_UNCOMMITTED: - case Connection.TRANSACTION_READ_COMMITTED: - case Connection.TRANSACTION_REPEATABLE_READ: - case Connection.TRANSACTION_SERIALIZABLE: - return level; - } - throw new IllegalArgumentException("Invalid transaction isolation value: " + transactionIsolationName); - } - catch (Exception e) { - //its name - } - try { - Field field = Connection.class.getField(transactionIsolationName); - return field.getInt(null); + String upperName = transactionIsolationName.toUpperCase(); + if (upperName.startsWith("TRANSACTION_")) { + Field field = Connection.class.getField(transactionIsolationName); + return field.getInt(null); + } + else { + int level = Integer.parseInt(transactionIsolationName); + //its number + switch (level) { + case Connection.TRANSACTION_READ_UNCOMMITTED: + case Connection.TRANSACTION_READ_COMMITTED: + case Connection.TRANSACTION_REPEATABLE_READ: + case Connection.TRANSACTION_SERIALIZABLE: + return level; + } + //invalid level + throw new IllegalArgumentException(); + } } catch (Exception e) { throw new IllegalArgumentException("Invalid transaction isolation value: " + transactionIsolationName); From acb7d41961875d4049b399e583fc44cfb74262f8 Mon Sep 17 00:00:00 2001 From: Nitin Date: Wed, 22 Jul 2015 20:59:22 +0530 Subject: [PATCH 09/14] done However, Doc for setTransactionIsolation() says: (Note that Connection.TRANSACTION_NONE cannot be used because it specifies that transactions are not supported.) --- .../java/com/zaxxer/hikari/pool/PoolElf.java | 1073 ++++++++--------- 1 file changed, 536 insertions(+), 537 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/pool/PoolElf.java b/src/main/java/com/zaxxer/hikari/pool/PoolElf.java index cc5158a0..8703af9d 100644 --- a/src/main/java/com/zaxxer/hikari/pool/PoolElf.java +++ b/src/main/java/com/zaxxer/hikari/pool/PoolElf.java @@ -1,537 +1,536 @@ -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 { - String upperName = transactionIsolationName.toUpperCase(); - if (upperName.startsWith("TRANSACTION_")) { - Field field = Connection.class.getField(transactionIsolationName); - return field.getInt(null); - } - else { - int level = Integer.parseInt(transactionIsolationName); - //its number - switch (level) { - case Connection.TRANSACTION_READ_UNCOMMITTED: - case Connection.TRANSACTION_READ_COMMITTED: - case Connection.TRANSACTION_REPEATABLE_READ: - case Connection.TRANSACTION_SERIALIZABLE: - return level; - } - //invalid level - throw new IllegalArgumentException(); - } - } - 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); - - connection.setAutoCommit(isAutoCommit); - if (isReadOnly != null) { - connection.setReadOnly(isReadOnly); - } - - int defaultLevel = connection.getTransactionIsolation(); - transactionIsolation = (transactionIsolation < 0 ? defaultLevel : transactionIsolation); - if (transactionIsolation != defaultLevel) { - 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(); - } -} +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 { + final String upperName = transactionIsolationName.toUpperCase(); + if (upperName.startsWith("TRANSACTION_")) { + Field field = Connection.class.getField(upperName); + return field.getInt(null); + } else { + final int level = Integer.parseInt(transactionIsolationName); + // its number + switch (level) { + case Connection.TRANSACTION_READ_UNCOMMITTED: + case Connection.TRANSACTION_READ_COMMITTED: + case Connection.TRANSACTION_REPEATABLE_READ: + case Connection.TRANSACTION_SERIALIZABLE: + case Connection.TRANSACTION_NONE: + return level; + default: + throw new IllegalArgumentException(); + } + } + } 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); + + connection.setAutoCommit(isAutoCommit); + if (isReadOnly != null) { + connection.setReadOnly(isReadOnly); + } + + int defaultLevel = connection.getTransactionIsolation(); + transactionIsolation = (transactionIsolation < 0 ? defaultLevel : transactionIsolation); + if (transactionIsolation != defaultLevel) { + 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(); + } +} From f6e9793034de90bd373e1065728604255b627fb2 Mon Sep 17 00:00:00 2001 From: Nitin Date: Wed, 22 Jul 2015 21:26:20 +0530 Subject: [PATCH 10/14] 2nd try. not sure why the whole file shown in diff before --- .../java/com/zaxxer/hikari/pool/PoolElf.java | 1074 +++++++++-------- 1 file changed, 538 insertions(+), 536 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/pool/PoolElf.java b/src/main/java/com/zaxxer/hikari/pool/PoolElf.java index 8703af9d..12e9f1e0 100644 --- a/src/main/java/com/zaxxer/hikari/pool/PoolElf.java +++ b/src/main/java/com/zaxxer/hikari/pool/PoolElf.java @@ -1,536 +1,538 @@ -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 { - final String upperName = transactionIsolationName.toUpperCase(); - if (upperName.startsWith("TRANSACTION_")) { - Field field = Connection.class.getField(upperName); - return field.getInt(null); - } else { - final int level = Integer.parseInt(transactionIsolationName); - // its number - switch (level) { - case Connection.TRANSACTION_READ_UNCOMMITTED: - case Connection.TRANSACTION_READ_COMMITTED: - case Connection.TRANSACTION_REPEATABLE_READ: - case Connection.TRANSACTION_SERIALIZABLE: - case Connection.TRANSACTION_NONE: - return level; - default: - throw new IllegalArgumentException(); - } - } - } 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); - - connection.setAutoCommit(isAutoCommit); - if (isReadOnly != null) { - connection.setReadOnly(isReadOnly); - } - - int defaultLevel = connection.getTransactionIsolation(); - transactionIsolation = (transactionIsolation < 0 ? defaultLevel : transactionIsolation); - if (transactionIsolation != defaultLevel) { - 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(); - } -} +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 { + final String upperName = transactionIsolationName.toUpperCase(); + if (upperName.startsWith("TRANSACTION_")) { + Field field = Connection.class.getField(upperName); + return field.getInt(null); + } + else { + final int level = Integer.parseInt(transactionIsolationName); + //its number + switch (level) { + case Connection.TRANSACTION_READ_UNCOMMITTED: + case Connection.TRANSACTION_READ_COMMITTED: + case Connection.TRANSACTION_REPEATABLE_READ: + case Connection.TRANSACTION_SERIALIZABLE: + case Connection.TRANSACTION_NONE: + return level; + default: + throw new IllegalArgumentException(); + } + } + } + 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); + + connection.setAutoCommit(isAutoCommit); + if (isReadOnly != null) { + connection.setReadOnly(isReadOnly); + } + + int defaultLevel = connection.getTransactionIsolation(); + transactionIsolation = (transactionIsolation < 0 ? defaultLevel : transactionIsolation); + if (transactionIsolation != defaultLevel) { + 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(); + } +} From 8eb6b7653453959e9b189feb21d9401f960650c0 Mon Sep 17 00:00:00 2001 From: Nitin Date: Wed, 22 Jul 2015 21:31:31 +0530 Subject: [PATCH 11/14] done now. please see changes together here: https://github.com/brettwooldridge/HikariCP/pull/347/files --- src/main/java/com/zaxxer/hikari/pool/PoolElf.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/zaxxer/hikari/pool/PoolElf.java b/src/main/java/com/zaxxer/hikari/pool/PoolElf.java index 12e9f1e0..7b8747c4 100644 --- a/src/main/java/com/zaxxer/hikari/pool/PoolElf.java +++ b/src/main/java/com/zaxxer/hikari/pool/PoolElf.java @@ -188,7 +188,7 @@ public final class PoolElf connection.setReadOnly(isReadOnly); } - int defaultLevel = connection.getTransactionIsolation(); + final int defaultLevel = connection.getTransactionIsolation(); transactionIsolation = (transactionIsolation < 0 ? defaultLevel : transactionIsolation); if (transactionIsolation != defaultLevel) { connection.setTransactionIsolation(transactionIsolation); From 0dbdc47f05c914a6437ab50ab19db5c2f0e61d5a Mon Sep 17 00:00:00 2001 From: Nitin Date: Wed, 22 Jul 2015 21:55:29 +0530 Subject: [PATCH 12/14] fixed test --- src/test/java/com/zaxxer/hikari/ShutdownTest.java | 2 +- src/test/java/com/zaxxer/hikari/TestValidation.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/zaxxer/hikari/ShutdownTest.java b/src/test/java/com/zaxxer/hikari/ShutdownTest.java index b399036e..424af6a1 100644 --- a/src/test/java/com/zaxxer/hikari/ShutdownTest.java +++ b/src/test/java/com/zaxxer/hikari/ShutdownTest.java @@ -225,7 +225,7 @@ public class ShutdownTest ds.getConnection(); } catch (SQLException e) { - Assert.assertTrue(e.getMessage().contains("has been shutdown")); + Assert.assertTrue(e.getMessage().contains("has been closed.")); } } diff --git a/src/test/java/com/zaxxer/hikari/TestValidation.java b/src/test/java/com/zaxxer/hikari/TestValidation.java index 0b280af9..e0c8207e 100644 --- a/src/test/java/com/zaxxer/hikari/TestValidation.java +++ b/src/test/java/com/zaxxer/hikari/TestValidation.java @@ -184,7 +184,7 @@ public class TestValidation Assert.fail(); } catch (IllegalStateException ise) { - Assert.assertTrue(ise.getMessage().contains("cannot use")); + Assert.assertTrue(ise.getMessage().contains("together")); } } From 31113bf0d908a8fc194f078dae57b81494db7126 Mon Sep 17 00:00:00 2001 From: Nitin Date: Thu, 23 Jul 2015 15:49:25 +0530 Subject: [PATCH 13/14] try 2 fixed test --- src/test/java/com/zaxxer/hikari/TestValidation.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/zaxxer/hikari/TestValidation.java b/src/test/java/com/zaxxer/hikari/TestValidation.java index e0c8207e..a3c65369 100644 --- a/src/test/java/com/zaxxer/hikari/TestValidation.java +++ b/src/test/java/com/zaxxer/hikari/TestValidation.java @@ -59,7 +59,7 @@ public class TestValidation config.validate(); Assert.fail(); } - catch (IllegalStateException ise) { + catch (IllegalArgumentException ise) { // pass } } @@ -183,7 +183,7 @@ public class TestValidation config.validate(); Assert.fail(); } - catch (IllegalStateException ise) { + catch (IllegalArgumentException ise) { Assert.assertTrue(ise.getMessage().contains("together")); } } From 9136e8e6dd2a63d376a64740253321a0d4932029 Mon Sep 17 00:00:00 2001 From: Nitin Date: Thu, 23 Jul 2015 16:52:02 +0530 Subject: [PATCH 14/14] removed else --- .../java/com/zaxxer/hikari/HikariConfig.java | 2 +- .../java/com/zaxxer/hikari/pool/PoolElf.java | 25 ++++++++----------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/HikariConfig.java b/src/main/java/com/zaxxer/hikari/HikariConfig.java index 665800d0..ea441326 100644 --- a/src/main/java/com/zaxxer/hikari/HikariConfig.java +++ b/src/main/java/com/zaxxer/hikari/HikariConfig.java @@ -824,7 +824,7 @@ public class HikariConfig implements HikariConfigMXBean private void logConfiguration() { - LOGGER.debug("HikariCP pool {} configuration:", poolName); + LOGGER.debug("{} - configuration:", poolName); final Set propertyNames = new TreeSet<>(PropertyElf.getPropertyNames(HikariConfig.class)); for (String prop : propertyNames) { try { diff --git a/src/main/java/com/zaxxer/hikari/pool/PoolElf.java b/src/main/java/com/zaxxer/hikari/pool/PoolElf.java index 7b8747c4..2031c26a 100644 --- a/src/main/java/com/zaxxer/hikari/pool/PoolElf.java +++ b/src/main/java/com/zaxxer/hikari/pool/PoolElf.java @@ -114,20 +114,17 @@ public final class PoolElf Field field = Connection.class.getField(upperName); return field.getInt(null); } - else { - final int level = Integer.parseInt(transactionIsolationName); - //its number - switch (level) { - case Connection.TRANSACTION_READ_UNCOMMITTED: - case Connection.TRANSACTION_READ_COMMITTED: - case Connection.TRANSACTION_REPEATABLE_READ: - case Connection.TRANSACTION_SERIALIZABLE: - case Connection.TRANSACTION_NONE: - return level; - default: - throw new IllegalArgumentException(); - } - } + final int level = Integer.parseInt(transactionIsolationName); + switch (level) { + case Connection.TRANSACTION_READ_UNCOMMITTED: + case Connection.TRANSACTION_READ_COMMITTED: + case Connection.TRANSACTION_REPEATABLE_READ: + case Connection.TRANSACTION_SERIALIZABLE: + case Connection.TRANSACTION_NONE: + return level; + default: + throw new IllegalArgumentException(); + } } catch (Exception e) { throw new IllegalArgumentException("Invalid transaction isolation value: " + transactionIsolationName);