You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
645 lines
23 KiB
Java
645 lines
23 KiB
Java
/*
|
|
* Copyright (C) 2013,2014 Brett Wooldridge
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.zaxxer.hikari.pool;
|
|
|
|
import static com.zaxxer.hikari.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.SQLTransientConnectionException;
|
|
import java.util.Collections;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
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.ThreadLocalRandom;
|
|
import java.util.concurrent.ThreadPoolExecutor;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import com.codahale.metrics.MetricRegistry;
|
|
import com.codahale.metrics.health.HealthCheckRegistry;
|
|
import com.zaxxer.hikari.HikariConfig;
|
|
import com.zaxxer.hikari.HikariPoolMXBean;
|
|
import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
|
|
import com.zaxxer.hikari.metrics.PoolStats;
|
|
import com.zaxxer.hikari.metrics.dropwizard.CodahaleHealthChecker;
|
|
import com.zaxxer.hikari.metrics.dropwizard.CodahaleMetricsTrackerFactory;
|
|
import com.zaxxer.hikari.util.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.SuspendResumeLock;
|
|
|
|
/**
|
|
* This is the primary connection pool class that provides the basic
|
|
* pooling behavior for HikariCP.
|
|
*
|
|
* @author Brett Wooldridge
|
|
*/
|
|
public class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateListener
|
|
{
|
|
private static final Logger LOGGER = LoggerFactory.getLogger(HikariPool.class);
|
|
|
|
private static final ClockSource clockSource = ClockSource.INSTANCE;
|
|
|
|
private static final long ALIVE_BYPASS_WINDOW_MS = Long.getLong("com.zaxxer.hikari.aliveBypassWindow", TimeUnit.SECONDS.toMillis(1));
|
|
private static 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;
|
|
|
|
private volatile int poolState;
|
|
|
|
private final AtomicInteger totalConnections;
|
|
private final ThreadPoolExecutor addConnectionExecutor;
|
|
private final ThreadPoolExecutor closeConnectionExecutor;
|
|
private final ScheduledThreadPoolExecutor houseKeepingExecutorService;
|
|
|
|
private final ConcurrentBag<PoolEntry> connectionBag;
|
|
|
|
private final ProxyLeakTask leakTask;
|
|
private final SuspendResumeLock suspendResumeLock;
|
|
|
|
private MetricsTrackerDelegate metricsTracker;
|
|
private boolean isRecordMetrics;
|
|
|
|
/**
|
|
* Construct a HikariPool with the specified configuration.
|
|
*
|
|
* @param config a HikariConfig instance
|
|
*/
|
|
public HikariPool(final HikariConfig config)
|
|
{
|
|
super(config);
|
|
|
|
this.connectionBag = new ConcurrentBag<>(this);
|
|
this.totalConnections = new AtomicInteger();
|
|
this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : 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.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
|
|
this.houseKeepingExecutorService.setRemoveOnCancelPolicy(true);
|
|
}
|
|
else {
|
|
this.houseKeepingExecutorService = config.getScheduledExecutorService();
|
|
}
|
|
|
|
this.houseKeepingExecutorService.scheduleAtFixedRate(new HouseKeeper(), HOUSEKEEPING_PERIOD_MS, HOUSEKEEPING_PERIOD_MS, TimeUnit.MILLISECONDS);
|
|
|
|
this.leakTask = new ProxyLeakTask(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
|
|
|
|
if (config.getMetricsTrackerFactory() != null) {
|
|
setMetricsTrackerFactory(config.getMetricsTrackerFactory());
|
|
}
|
|
else {
|
|
setMetricRegistry(config.getMetricRegistry());
|
|
}
|
|
|
|
setHealthCheckRegistry(config.getHealthCheckRegistry());
|
|
|
|
registerMBeans(this);
|
|
|
|
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;
|
|
do {
|
|
final PoolEntry poolEntry = connectionBag.borrow(timeout, TimeUnit.MILLISECONDS);
|
|
if (poolEntry == null) {
|
|
break; // We timed out... break and throw exception
|
|
}
|
|
|
|
final long now = clockSource.currentTime();
|
|
if (poolEntry.isMarkedEvicted() || (clockSource.elapsedMillis(poolEntry.lastAccessed, now) > ALIVE_BYPASS_WINDOW_MS && !isConnectionAlive(poolEntry.connection))) {
|
|
closeConnection(poolEntry, "(connection evicted or dead)"); // Throw away the dead connection and try again
|
|
timeout = hardTimeout - clockSource.elapsedMillis(startTime);
|
|
}
|
|
else {
|
|
metricsTracker.recordBorrowStats(poolEntry, startTime);
|
|
return poolEntry.createProxyConnection(leakTask.start(poolEntry), now);
|
|
}
|
|
}
|
|
while (timeout > 0L);
|
|
}
|
|
catch (InterruptedException e) {
|
|
throw new SQLException(poolName + " - Interrupted during connection acquisition", e);
|
|
}
|
|
finally {
|
|
suspendResumeLock.release();
|
|
}
|
|
|
|
logPoolState("Timeout failure\t");
|
|
|
|
String sqlState = null;
|
|
final Throwable originalException = getLastConnectionFailure();
|
|
if (originalException instanceof SQLException) {
|
|
sqlState = ((SQLException) originalException).getSQLState();
|
|
}
|
|
final SQLException connectionException = new SQLTransientConnectionException(poolName + " - Connection is not available, request timed out after " + clockSource.elapsedMillis(startTime) + "ms.", sqlState, originalException);
|
|
if (originalException instanceof SQLException) {
|
|
connectionException.setNextException((SQLException) originalException);
|
|
}
|
|
throw connectionException;
|
|
}
|
|
|
|
/**
|
|
* Release a connection back to the pool, or permanently close it if it is broken.
|
|
*
|
|
* @param poolEntry the PoolBagEntry to release back to the pool
|
|
*/
|
|
public final void releaseConnection(final PoolEntry poolEntry)
|
|
{
|
|
metricsTracker.recordConnectionUsage(poolEntry);
|
|
|
|
connectionBag.requite(poolEntry);
|
|
}
|
|
|
|
/**
|
|
* 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\t");
|
|
|
|
connectionBag.close();
|
|
softEvictConnections();
|
|
addConnectionExecutor.shutdown();
|
|
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);
|
|
}
|
|
|
|
shutdownNetworkTimeoutExecutor();
|
|
closeConnectionExecutor.shutdown();
|
|
closeConnectionExecutor.awaitTermination(5L, TimeUnit.SECONDS);
|
|
}
|
|
finally {
|
|
logPoolState("After closing\t");
|
|
|
|
unregisterMBeans();
|
|
metricsTracker.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Evict a connection from the pool.
|
|
*
|
|
* @param proxyConnection the connection to evict
|
|
*/
|
|
public final void evictConnection(Connection proxyConnection)
|
|
{
|
|
softEvictConnection(((ProxyConnection) proxyConnection).getPoolEntry(), "(connection evicted by user)", true /* owner */);
|
|
}
|
|
|
|
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 = new MetricsTrackerDelegate(metricsTrackerFactory.create(config.getPoolName(), getPoolStats()));
|
|
}
|
|
else {
|
|
this.metricsTracker = new NopMetricsTrackerDelegate();
|
|
}
|
|
}
|
|
|
|
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());
|
|
}
|
|
}
|
|
|
|
// ***********************************************************************
|
|
// IBagStateListener callback
|
|
// ***********************************************************************
|
|
|
|
/** {@inheritDoc} */
|
|
@Override
|
|
public Future<Boolean> addBagItem()
|
|
{
|
|
FutureTask<Boolean> 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) (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 (PoolEntry poolEntry : connectionBag.values()) {
|
|
softEvictConnection(poolEntry, "(connection evicted by user)", false /* not owner */);
|
|
}
|
|
}
|
|
|
|
/** {@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 poolEntry the connection to actually close
|
|
*/
|
|
void closeConnection(final PoolEntry poolEntry, final String closureReason)
|
|
{
|
|
final Connection connection = poolEntry.connection;
|
|
poolEntry.close();
|
|
if (connectionBag.remove(poolEntry)) {
|
|
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() {
|
|
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
|
|
return true;
|
|
}
|
|
|
|
try {
|
|
final PoolEntry poolEntry = newPoolEntry();
|
|
connectionBag.add(poolEntry);
|
|
|
|
final long maxLifetime = config.getMaxLifetime();
|
|
if (maxLifetime > 0) {
|
|
final long variance = maxLifetime > 60_000 ? ThreadLocalRandom.current().nextLong(10_000) : 0;
|
|
final long lifetime = maxLifetime - variance;
|
|
poolEntry.setFutureEol(houseKeepingExecutorService.schedule(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
softEvictConnection(poolEntry, "(connection reached maxLifetime)", false /* not owner */);
|
|
}
|
|
}, lifetime, TimeUnit.MILLISECONDS));
|
|
}
|
|
|
|
LOGGER.debug("{} - Added connection {}", poolName, poolEntry.connection);
|
|
return true;
|
|
}
|
|
catch (Exception e) {
|
|
totalConnections.decrementAndGet(); // We failed, so undo speculative increment of totalConnections
|
|
if (poolState == POOL_NORMAL) {
|
|
LOGGER.debug("{} - Cannot acquire connection from data source", poolName, e);
|
|
}
|
|
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\t");
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attempt to abort() active connections, or close() them.
|
|
*/
|
|
private void abortActiveConnections(final ExecutorService assassinExecutor)
|
|
{
|
|
for (PoolEntry poolEntry : connectionBag.values(STATE_IN_USE)) {
|
|
try {
|
|
poolEntry.markEvicted();
|
|
poolEntry.connection.abort(assassinExecutor);
|
|
}
|
|
catch (Throwable e) {
|
|
quietlyCloseConnection(poolEntry.connection, "(connection aborted during shutdown)");
|
|
}
|
|
finally {
|
|
poolEntry.close();
|
|
if (connectionBag.remove(poolEntry)) {
|
|
totalConnections.decrementAndGet();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fill the pool up to the minimum size.
|
|
*/
|
|
private void initializeConnections()
|
|
{
|
|
if (config.isInitializationFailFast()) {
|
|
try {
|
|
if (!addConnection()) {
|
|
throw getLastConnectionFailure();
|
|
}
|
|
|
|
final PoolEntry poolEntry = connectionBag.borrow(connectionTimeout, TimeUnit.MILLISECONDS);
|
|
if (config.getMinimumIdle() == 0) {
|
|
closeConnection(poolEntry, "Initialization validation complete, closing test connection.");
|
|
}
|
|
else {
|
|
connectionBag.requite(poolEntry);
|
|
}
|
|
}
|
|
catch (Throwable e) {
|
|
try {
|
|
shutdown();
|
|
}
|
|
catch (Throwable ex) {
|
|
e.addSuppressed(ex);
|
|
}
|
|
|
|
throw new PoolInitializationException(e);
|
|
}
|
|
}
|
|
|
|
fillPool();
|
|
}
|
|
|
|
private void softEvictConnection(final PoolEntry poolEntry, final String reason, final boolean owner)
|
|
{
|
|
poolEntry.markEvicted();
|
|
if (connectionBag.reserve(poolEntry) || owner) {
|
|
closeConnection(poolEntry, reason);
|
|
}
|
|
}
|
|
|
|
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();
|
|
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);
|
|
previous = now;
|
|
softEvictConnections();
|
|
fillPool();
|
|
return;
|
|
}
|
|
|
|
previous = now;
|
|
|
|
logPoolState("Before cleanup\t");
|
|
|
|
if (idleTimeout > 0L) {
|
|
final List<PoolEntry> notInUseList = connectionBag.values(STATE_NOT_IN_USE);
|
|
int removable = notInUseList.size() - config.getMinimumIdle();
|
|
if (removable > 0) {
|
|
// Sort pool entries on lastAccessed
|
|
Collections.sort(notInUseList, PoolEntry.LASTACCESS_COMPARABLE);
|
|
// Iterate the first N removable elements
|
|
for (final Iterator<PoolEntry> iter = notInUseList.iterator(); removable > 0 && iter.hasNext(); ) {
|
|
final PoolEntry poolEntry = iter.next();
|
|
if (clockSource.elapsedMillis(poolEntry.lastAccessed, now) > idleTimeout && connectionBag.reserve(poolEntry)) {
|
|
closeConnection(poolEntry, "(connection passed idleTimeout)");
|
|
removable--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
logPoolState("After cleanup\t");
|
|
|
|
fillPool(); // Try to maintain minimum connections
|
|
}
|
|
}
|
|
|
|
public static class PoolInitializationException extends RuntimeException
|
|
{
|
|
private static final long serialVersionUID = 929872118275916520L;
|
|
|
|
/**
|
|
* Construct an exception, possibly wrapping the provided Throwable as the cause.
|
|
* @param t the Throwable to wrap
|
|
*/
|
|
public PoolInitializationException(Throwable t)
|
|
{
|
|
super("Exception during pool initialization: " + t.getMessage(), t);
|
|
}
|
|
}
|
|
}
|