ConnectionPool.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.jdbc.pool;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import javax.sql.XAConnection;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.interceptor.QueryTimeoutInterceptor;
import org.apache.tomcat.jdbc.pool.interceptor.StatementCache;
import org.apache.tomcat.jdbc.pool.util.MessageFactory;
/**
* Implementation of simple connection pool. The ConnectionPool uses a {@link PoolProperties} object for storing all the
* meta information about the connection pool. As the underlying implementation, the connection pool uses
* {@link java.util.concurrent.BlockingQueue} to store active and idle connections. A custom implementation of a fair
* {@link FairBlockingQueue} blocking queue is provided with the connection pool itself.
*/
public class ConnectionPool {
/**
* Default domain for objects registering with an mbean server
*/
public static final String POOL_JMX_DOMAIN = "tomcat.jdbc";
/**
* Prefix type for JMX registration
*/
public static final String POOL_JMX_TYPE_PREFIX = POOL_JMX_DOMAIN + ":type=";
/**
* Logger
*/
private static final Log log = LogFactory.getLog(ConnectionPool.class);
/**
* The JDBC 4.3 <code>beginRequest()</code> hint, looked up by reflection because this module compiles against Java
* 8, whose {@link java.sql.Connection} does not declare it; on a runtime that predates JDBC 4.3 the lookup yields
* null and the hint is not sent.
*/
private static final Method BEGIN_REQUEST = getRequestHintMethod("beginRequest");
/**
* The counterpart of {@link #BEGIN_REQUEST}, sent when the application returns the connection to the pool.
*/
private static final Method END_REQUEST = getRequestHintMethod("endRequest");
/**
* Carries the size of the pool, instead of relying on a queue implementation that usually iterates over to get an
* exact count
*/
private AtomicInteger size = new AtomicInteger(0);
/**
* All the information about the connection pool These are the properties the pool got instantiated with
*/
private PoolConfiguration poolProperties;
/**
* Contains all the connections that are in use TODO - this shouldn't be a blocking queue, simply a list to hold our
* objects
*/
private BlockingQueue<PooledConnection> busy;
/**
* Contains all the idle connections
*/
private BlockingQueue<PooledConnection> idle;
/**
* The thread that is responsible for checking abandoned and idle threads
*/
private PoolCleaner poolCleaner;
/**
* Pool closed flag
*/
private volatile boolean closed = false;
/**
* Since newProxyInstance performs the same operation, over and over again, it is much more optimized if we simply
* store the constructor ourselves. One constructor is cached per proxy kind (with and without the XAConnection
* interface), so that a pool whose backing data source changes kind at runtime keeps proxying each connection
* correctly.
*/
private volatile Constructor<?> proxyClassConstructor;
/**
* Cached constructor for proxies that also implement XAConnection.
*/
private volatile Constructor<?> xaProxyClassConstructor;
/**
* Executor service used to cancel Futures
*/
private ThreadPoolExecutor cancellator = new ThreadPoolExecutor(0, 1, 1000, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>());
/**
* reference to the JMX mbean. close() gives it up without holding any pool lock while other threads still notify
* through it, so the field is volatile and every user reads it at most once, into a local.
*/
protected volatile org.apache.tomcat.jdbc.pool.jmx.ConnectionPool jmxPool = null;
/**
* counter to track how many threads are waiting for a connection
*/
private AtomicInteger waitcount = new AtomicInteger(0);
private AtomicLong poolVersion = new AtomicLong(Long.MIN_VALUE);
/**
* The counters for statistics of the pool.
*/
private final AtomicLong borrowedCount = new AtomicLong(0);
private final AtomicLong returnedCount = new AtomicLong(0);
private final AtomicLong createdCount = new AtomicLong(0);
private final AtomicLong releasedCount = new AtomicLong(0);
private final AtomicLong reconnectedCount = new AtomicLong(0);
private final AtomicLong removeAbandonedCount = new AtomicLong(0);
private final AtomicLong releasedIdleCount = new AtomicLong(0);
/**
* Instantiate a connection pool. This will create connections if initialSize is larger than 0. The
* {@link PoolProperties} should not be reused for another connection pool.
*
* @param prop PoolProperties - all the properties for this connection pool
*
* @throws SQLException Pool initialization error
*/
public ConnectionPool(PoolConfiguration prop) throws SQLException {
// Setup quick access variables and pools
init(prop);
}
/**
* Retrieves a Connection future. If a connection is not available, one can block using future.get() until a
* connection has become available. If a connection is not retrieved, the Future must be cancelled in order for the
* connection to be returned to the pool. Closing the pool cancels a future that has not received a connection yet,
* so a thread blocked in {@link Future#get()} observes a {@link java.util.concurrent.CancellationException} instead
* of waiting for a connection that can no longer be handed out.
*
* @return a Future containing a reference to the connection or the future connection
*
* @throws SQLException Cannot use asynchronous connect
*/
public Future<Connection> getConnectionAsync() throws SQLException {
try {
PooledConnection pc = borrowConnection(0, null, null);
if (pc != null) {
return new ConnectionFuture(pc);
}
} catch (SQLException x) {
// JDBC exceptions may carry no message at all; a missing
// "NoWait" marker must be assumed for those, so that the
// original failure reaches the caller instead of a
// NullPointerException from dereferencing the message
String msg = x.getMessage();
if (msg == null || msg.indexOf("NoWait") < 0) {
throw x;
}
}
// We can only retrieve a future if the underlying queue supports it.
if (idle instanceof FairBlockingQueue<?>) {
Future<PooledConnection> pcf = ((FairBlockingQueue<PooledConnection>) idle).pollAsync();
if (isClosed() && pcf.isCancelled()) {
// The pool was closed between the borrow attempt above and the
// registration of the waiter: close() fails every waiter the
// queue holds and a later poll cannot register one, so this
// future will never hand out a connection. Reject the borrow the
// same way a synchronous borrow on a closed pool does, rather than
// handing out a future that only ever fails with a cancellation.
throw new SQLException(MessageFactory.get("connectionPool.poolClosed"));
}
return new ConnectionFuture(pcf);
} else {
throw new SQLException(MessageFactory.get("connectionPool.poolMisconfigured"));
}
}
/**
* Borrows a connection from the pool. If a connection is available (in the idle queue) or the pool has not reached
* {@link PoolProperties#maxActive maxActive} connections a connection is returned immediately. If no connection is
* available, the pool will attempt to fetch a connection for {@link PoolProperties#maxWait maxWait} milliseconds.
*
* @return Connection - a java.sql.Connection/javax.sql.PooledConnection reflection proxy, wrapping the underlying
* object.
*
* @throws SQLException - if the wait times out or a failure occurs creating a connection
*/
public Connection getConnection() throws SQLException {
// Check out a connection
PooledConnection con = borrowConnection(-1, null, null);
return setupConnection(con);
}
/**
* Borrows a connection from the pool. If a connection is available (in the idle queue) or the pool has not reached
* {@link PoolProperties#maxActive maxActive} connections a connection is returned immediately. If no connection is
* available, the pool will attempt to fetch a connection for {@link PoolProperties#maxWait maxWait} milliseconds.
*
* @param username The user name to use for the connection
* @param password The password for the connection
*
* @return Connection - a java.sql.Connection/javax.sql.PooledConnection reflection proxy, wrapping the underlying
* object.
*
* @throws SQLException - if the wait times out or a failure occurs creating a connection
*/
public Connection getConnection(String username, String password) throws SQLException {
// Check out a connection
PooledConnection con = borrowConnection(-1, username, password);
return setupConnection(con);
}
/**
* Returns the name of this pool
*
* @return String - the name of the pool
*/
public String getName() {
return getPoolProperties().getPoolName();
}
/**
* Return the number of threads waiting for a connection
*
* @return number of threads waiting for a connection
*/
public int getWaitCount() {
return waitcount.get();
}
/**
* Returns the pool properties associated with this connection pool
*
* @return PoolProperties
*/
public PoolConfiguration getPoolProperties() {
return this.poolProperties;
}
/**
* Returns the total size of this pool, this includes both busy and idle connections
*
* @return int - number of established connections to the database
*/
public int getSize() {
return size.get();
}
/**
* Returns the number of connections that are in use
*
* @return int - number of established connections that are being used by the application
*/
public int getActive() {
return busy.size();
}
/**
* Returns the number of idle connections
*
* @return int - number of established connections not being used
*/
public int getIdle() {
return idle.size();
}
/**
* Returns true if {@link #close close} has been called, and the connection pool is unusable
*
* @return boolean
*/
public boolean isClosed() {
return this.closed;
}
/**
* configures a pooled connection as a proxy. This Proxy implements {@link java.sql.Connection} and
* {@link javax.sql.PooledConnection} interfaces. All calls on {@link java.sql.Connection} methods will be
* propagated down to the actual JDBC connection except for the {@link java.sql.Connection#close()} method.
*
* @param con a {@link PooledConnection} to wrap in a Proxy
*
* @return a {@link java.sql.Connection} object wrapping a pooled connection.
*
* @throws SQLException if an interceptor can't be configured, if the proxy can't be instantiated
*/
protected Connection setupConnection(PooledConnection con) throws SQLException {
// The connection is being handed to a borrower: a borrow is counted
// here rather than where it was claimed, as that is the one point all
// hand-offs pass - the synchronous borrows, the async borrow that
// takes an idle connection, the configuration of a connection a queue
// handed to an async poll, and the cleanup that retrieves a cancelled
// poll's connection to return it - each exactly once, while a claim
// that ends in a throw never reaches a borrower at all. A setup that
// fails gives the connection back through abandonSetup(), which counts
// the return, so the borrow is counted on the way in to match. The
// pool's own initialization is the only path that takes connections
// out of the creation route without a borrower: it returns its
// preloaded connections directly and never sets one up here, so it
// stays out of the count.
borrowedCount.incrementAndGet();
// Fetch previously cached interceptor proxy - one per connection
JdbcInterceptor handler = con.getHandler();
if (handler == null) {
// The registration reads the pool's own JMX object itself and does
// nothing once close() has given it up; it is not gated on jmxPool
// here because checking the field and then calling into the
// connection would let close() take it away between the two, and
// the resulting failure must not strand the connection - it is
// already in busy, so a throw that skips abandonSetup() costs a
// physical connection and a maxActive slot until the pool restarts.
// A connection that connected while the pool's MBean was already
// published registered itself as part of connect() and is a no-op
// here; this call is the retry for one that could not - because the
// pool's MBean was published only after it connected, or because the
// server refused the name, and a refused publish must be retried
// once the obstacle is gone rather than count as a registration.
try {
con.createMBean();
} catch (Exception x) {
SQLException sx = new SQLException(MessageFactory.get("connectionPool.unableToRegisterMBean"));
sx.initCause(x);
// Nothing has been done to the connection, it can go back to the pool
abandonSetup(con, sx, false);
throw sx;
}
// Build the proxy handler
handler = new ProxyConnection(this, con, getPoolProperties().isUseEquals());
// Set up the interceptor chain
PoolProperties.InterceptorDefinition[] proxies = getPoolProperties().getJdbcInterceptorsAsArray();
for (int i = proxies.length - 1; i >= 0; i--) {
try {
// Create a new instance
JdbcInterceptor interceptor = proxies[i].getInterceptorClass().getConstructor().newInstance();
// Configure properties
interceptor.setProperties(proxies[i].getProperties());
// Setup the chain
interceptor.setNext(handler);
// Configure the last one to be held by the connection
handler = interceptor;
} catch (Exception x) {
SQLException sx = new SQLException(MessageFactory.get("connectionPool.unableToInstantiateInterceptorChain"));
sx.initCause(x);
// Nothing has been done to the connection, it can go back to the pool
abandonSetup(con, sx, false);
throw sx;
}
}
// Cache handler for the next iteration
con.setHandler(handler);
}
// Reset the chain in the order the interceptors are declared, for new and cached handlers alike
JdbcInterceptor next = handler;
try {
while (next != null) {
next.reset(this, con);
next = next.getNext();
}
} catch (Exception x) {
SQLException sx = new SQLException(MessageFactory.get("connectionPool.unableToResetInterceptor",
(next != null ? next.getClass().getName() : "unknown")));
sx.initCause(x);
// The state of the connection is unknown, discard it so its partially reset chain is dropped
abandonSetup(con, sx, true);
throw sx;
}
// Setup statement proxy
// The statement facade is always used; statement and result set
// handles a borrower keeps after returning the connection must stay
// inert, as the interceptor chain below is cached on the pooled
// connection and re-armed for the next borrower. (The
// useStatementFacade property that used to switch this off is
// deprecated and no longer has any effect.)
handler = new StatementFacade(handler);
try {
Constructor<?> proxyConstructor = getProxyConstructor(con.getXAConnection() != null);
// Create the proxy
// TODO possible optimization, keep track if this connection was returned properly, and don't generate a new
// handle and facade
// The disposable connection facade is always used; a handle that
// its borrower has closed must stay inert, as the interceptor
// chain below is cached on the pooled connection and re-armed
// for the next borrower. (The useDisposableConnectionFacade
// property that used to switch this off is deprecated and no
// longer has any effect.)
Connection connection = (Connection) proxyConstructor
.newInstance(new Object[] { new DisposableConnectionFacade(handler) });
// The handle exists and the borrow is about to start: send the
// JDBC 4.3 hint that opens the request, so a driver that
// optimises per-request session handling knows the unit of work
// begins here. The matching endRequest() is sent at the return,
// in returnConnection().
invokeRequestHint(BEGIN_REQUEST, con.getConnection());
// Return the connection
return connection;
} catch (Exception x) {
SQLException s = new SQLException();
s.initCause(x);
abandonSetup(con, s, false);
throw s;
}
}
/**
* Gives up a connection that was borrowed but could not be set up, so that it does not stay borrowed forever. Any
* failure to do so is added to the exception that is about to be thrown rather than hiding it.
*
* @param con the borrowed connection
* @param cause the exception that is about to be thrown to the caller
* @param discard {@code true} if the connection must be closed, {@code false} if it can go back to the pool
*/
private void abandonSetup(PooledConnection con, SQLException cause, boolean discard) {
try {
if (discard) {
con.setDiscarded(true);
}
returnConnection(con);
} catch (Exception y) {
cause.addSuppressed(y);
}
}
/**
* Looks up one of the JDBC 4.3 request boundary hint methods on {@link java.sql.Connection} by name, as they are
* absent from the Java 8 API this module compiles against.
*
* @param methodName the name of the hint method
*
* @return the method, or <code>null</code> on a runtime that predates JDBC 4.3
*/
private static Method getRequestHintMethod(String methodName) {
try {
return Connection.class.getMethod(methodName);
} catch (NoSuchMethodException x) {
// Java 8 and earlier: there are no request boundary hints to send
return null;
}
}
/**
* Sends one of the JDBC 4.3 request boundary hints to a physical connection. The hint is advisory - the
* specification lets the driver treat it as a no-op - so a driver that refuses one does not fail the borrow or the
* return around it; the refusal is logged with the driver's own cause.
*
* @param hint one of the hint methods, or <code>null</code> when running where they do not exist
* @param connection the physical connection to hint
*/
private static void invokeRequestHint(Method hint, Connection connection) {
if (hint == null) {
return;
}
try {
hint.invoke(connection);
} catch (InvocationTargetException x) {
log.warn(MessageFactory.get("connectionPool.requestHintRefused", hint.getName()),
x.getTargetException());
} catch (ReflectiveOperationException | IllegalArgumentException x) {
log.warn(MessageFactory.get("connectionPool.unableToSendRequestHint", hint.getName()), x);
}
}
/**
* Creates and caches a {@link java.lang.reflect.Constructor} used to instantiate the proxy object of the requested
* kind (with or without the XAConnection interface). We cache these, since the creation of a constructor is fairly
* slow.
*
* @param xa Use a XA connection
*
* @return constructor used to instantiate the wrapper object
*
* @throws NoSuchMethodException Failed to get a constructor
*/
@SuppressWarnings("deprecation")
public Constructor<?> getProxyConstructor(boolean xa) throws NoSuchMethodException {
// Cache the constructor per proxy kind
Constructor<?> constructor = xa ? xaProxyClassConstructor : proxyClassConstructor;
if (constructor == null) {
Class<?> proxyClass = xa
? Proxy.getProxyClass(ConnectionPool.class.getClassLoader(),
new Class[] { Connection.class, javax.sql.PooledConnection.class, XAConnection.class })
: Proxy.getProxyClass(ConnectionPool.class.getClassLoader(),
new Class[] { Connection.class, javax.sql.PooledConnection.class });
constructor = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
if (xa) {
xaProxyClassConstructor = constructor;
} else {
proxyClassConstructor = constructor;
}
}
return constructor;
}
/**
* Closes the pool and all disconnects all idle connections Active connections will be closed upon the
* {@link java.sql.Connection#close close} method is called on the underlying connection instead of being returned
* to the pool
*
* @param force - true to even close the active connections
*/
protected void close(boolean force) {
// Are we already closed
if (this.closed) {
return;
}
// Prevent other threads from entering
this.closed = true;
// Stop background thread
terminatePoolCleaner();
// An asynchronous borrow waits on the idle queue, where a thread cannot
// observe the closed flag: this pool never offers a connection to that
// queue once it is closed, so a caller parked in the Future.get() of
// getConnectionAsync() would stay parked forever. Fail the waiters that
// are registered on the queue, so that the futures handed out terminate.
if (idle instanceof FairBlockingQueue<?>) {
((FairBlockingQueue<PooledConnection>) idle).cancelWaiters();
}
// Release all idle connections
BlockingQueue<PooledConnection> pool = (!idle.isEmpty()) ? idle : (force ? busy : idle);
boolean interrupted = false;
while (!pool.isEmpty()) {
try {
// Retrieve the next connection
PooledConnection con = pool.poll(1000, TimeUnit.MILLISECONDS);
// Close it and retrieve the next one, if one is available
while (con != null) {
// Close the connection. A forced shutdown tears a busy
// connection away from a borrower that is legitimately
// using it, so the drain releases it the way it releases
// an idle one: disconnecting the session and running the
// interceptors' disconnect events. It does not abandon it.
// The abandonment counter, the WARN log and the JMX
// abandon notification record an application that leaked
// a connection - checkAbandoned()'s bookkeeping - and a
// shutdown the operator ordered is no such leak, so
// recording it there inflates the monitoring counter and
// fires one bogus leak notification per connection in
// use. The borrower's handle is left equally unusable
// either way: an abandon disconnected it through this
// same release, and a later close on the handle finds the
// pool closed and the connection already released.
try {
release(con);
} catch (Throwable x) {
// One failing connection must not leave the
// remaining idle and busy connections undisconnected
log.warn(MessageFactory.get("connectionPool.unableToClosePooledConnection"), x);
}
if (!pool.isEmpty()) {
con = pool.poll(1000, TimeUnit.MILLISECONDS);
} else {
break;
}
}
} catch (InterruptedException ex) {
// Do not restore the interrupt status here. A poll on a queue
// that checks the status before inspecting its elements would
// then throw again immediately, removing no connection, and
// the loop would spin without progress. Clear the status so
// the polls keep working and remember that we were interrupted
// so the status can be propagated once the drain completes.
Thread.interrupted();
interrupted = true;
}
if (pool.isEmpty() && force && pool != busy) {
pool = busy;
}
}
if (interrupted && getPoolProperties().getPropagateInterruptState()) {
Thread.currentThread().interrupt();
}
if (this.getPoolProperties().isJmxEnabled()) {
this.jmxPool = null;
}
PoolProperties.InterceptorDefinition[] proxies = getPoolProperties().getJdbcInterceptorsAsArray();
for (int i = 0; i < proxies.length; i++) {
try {
JdbcInterceptor interceptor = proxies[i].getInterceptorClass().getConstructor().newInstance();
interceptor.setProperties(proxies[i].getProperties());
interceptor.poolClosed(this);
} catch (Exception x) {
if (log.isDebugEnabled()) {
log.debug("Unable to inform interceptor of pool closure.", x);
}
}
}
}
/**
* Initialize the connection pool - called from the constructor
*
* @param properties PoolProperties - properties used to initialize the pool with
*
* @throws SQLException if initialization fails
*/
protected void init(PoolConfiguration properties) throws SQLException {
poolProperties = properties;
// Make sure the pool is properly configured
checkPoolConfiguration(properties);
// Make space for 10 extra in case we flow over a bit
busy = new LinkedBlockingQueue<>();
// busy = new FairBlockingQueue<PooledConnection>();
// Make space for 10 extra in case we flow over a bit
if (properties.isFairQueue()) {
idle = new FairBlockingQueue<>();
// idle = new LinkedTransferQueue<PooledConnection>();
// idle = new ArrayBlockingQueue<PooledConnection>(properties.getMaxActive(),false);
} else {
idle = new LinkedBlockingQueue<>();
}
initializePoolCleaner(properties);
// Create JMX MBean
if (this.getPoolProperties().isJmxEnabled()) {
createMBean();
}
// Parse and create an initial set of interceptors. Letting them know the pool has started.
// These interceptors will not get any connection.
PoolProperties.InterceptorDefinition[] proxies = getPoolProperties().getJdbcInterceptorsAsArray();
for (int i = 0; i < proxies.length; i++) {
try {
if (log.isDebugEnabled()) {
log.debug("Creating interceptor instance of class:" + proxies[i].getInterceptorClass());
}
JdbcInterceptor interceptor = proxies[i].getInterceptorClass().getConstructor().newInstance();
interceptor.setProperties(proxies[i].getProperties());
interceptor.poolStarted(this);
} catch (Exception x) {
log.error(MessageFactory.get("connectionPool.unableToInformInterceptorOfPoolStart"), x);
org.apache.tomcat.jdbc.pool.jmx.ConnectionPool jmx = jmxPool;
if (jmx != null) {
jmx.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.NOTIFY_INIT, getStackTrace(x));
}
close(true);
SQLException ex = new SQLException();
ex.initCause(x);
throw ex;
}
}
// Every interceptor class has now been loaded and instantiated above,
// so the parsed definitions can be compared against each other. The
// relative order of two interceptors in the chain is not something a
// single interceptor can see; only the pool holds the ordered list.
checkInterceptorOrder(proxies);
// Initialize the pool with its initial set of members
PooledConnection[] initialPool = new PooledConnection[poolProperties.getInitialSize()];
try {
for (int i = 0; i < initialPool.length; i++) {
initialPool[i] = this.borrowConnection(0, null, null); // Don't wait, should be no contention
}
} catch (SQLException x) {
log.error(MessageFactory.get("connectionPool.unableToCreateInitialConnections"), x);
if (!poolProperties.isIgnoreExceptionOnPreLoad()) {
org.apache.tomcat.jdbc.pool.jmx.ConnectionPool jmx = jmxPool;
if (jmx != null) {
jmx.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.NOTIFY_INIT, getStackTrace(x));
}
close(true);
throw x;
}
} finally {
// Return the members as idle to the pool
for (int i = 0; i < initialPool.length; i++) {
if (initialPool[i] != null) {
try {
this.returnConnection(initialPool[i]);
} catch (Exception x) {
/* NOOP */}
}
}
}
closed = false;
}
/**
* Warns once at pool start when the configured interceptor chain lets the statement cache answer a cache hit
* before the query timeout interceptor can re-apply its timeout. A
* {@link org.apache.tomcat.jdbc.pool.interceptor.StatementCache} hit is served without invoking the rest of the
* chain, and a statement recycled into the cache carries query timeout zero, so a
* {@link org.apache.tomcat.jdbc.pool.interceptor.QueryTimeoutInterceptor} listed after the cache never sees the
* reuse and every cached statement executes with no timeout. The chain is built back-to-front in
* {@link #setupConnection(PooledConnection)}, making the first-listed interceptor the outermost one, so the
* timeout interceptor must be listed before the cache. This detection only reports the misconfiguration; no
* behaviour is changed.
*
* @param proxies the parsed interceptor definitions, in configuration order
*/
private void checkInterceptorOrder(PoolProperties.InterceptorDefinition[] proxies) {
int firstCache = -1;
int lastTimeout = -1;
for (int i = 0; i < proxies.length; i++) {
try {
Class<? extends JdbcInterceptor> interceptorClass = proxies[i].getInterceptorClass();
if (StatementCache.class == interceptorClass) {
if (firstCache < 0) {
firstCache = i;
}
} else if (QueryTimeoutInterceptor.class == interceptorClass) {
lastTimeout = i;
}
} catch (ReflectiveOperationException | LinkageError x) {
// The pool-start loop above has already loaded and instantiated every one of these classes, so
// resolution cannot fail here. This check is diagnostic only; if it ever does fail, say nothing
// rather than disturbing a pool that started successfully.
return;
}
}
if (firstCache >= 0 && lastTimeout > firstCache) {
log.warn(MessageFactory.get("connectionPool.statementCacheBeforeQueryTimeout"));
}
}
/**
* Validates and corrects pool configuration values. Ensures that maxActive, initialSize, minIdle, maxIdle, and
* related values are consistent.
*
* @param properties the pool configuration to validate and correct
*/
public void checkPoolConfiguration(PoolConfiguration properties) {
// Make sure the pool is properly configured
if (properties.getMaxActive() < 1) {
log.warn(MessageFactory.get("connectionPool.maxActiveTooSmall", PoolProperties.DEFAULT_MAX_ACTIVE));
properties.setMaxActive(PoolProperties.DEFAULT_MAX_ACTIVE);
}
if (properties.getMaxActive() < properties.getInitialSize()) {
log.warn(MessageFactory.get("connectionPool.initialSizeTooLarge", properties.getMaxActive()));
properties.setInitialSize(properties.getMaxActive());
}
if (properties.getMinIdle() > properties.getMaxActive()) {
log.warn(MessageFactory.get("connectionPool.minIdleTooLarge", properties.getMaxActive()));
properties.setMinIdle(properties.getMaxActive());
}
if (properties.getMaxIdle() > properties.getMaxActive()) {
log.warn(MessageFactory.get("connectionPool.maxIdleTooLarge", properties.getMaxActive()));
properties.setMaxIdle(properties.getMaxActive());
}
if (properties.getMaxIdle() < properties.getMinIdle()) {
log.warn(MessageFactory.get("connectionPool.maxIdleTooSmall", properties.getMinIdle()));
properties.setMaxIdle(properties.getMinIdle());
}
if (properties.getMaxAge() > 0 && properties.isPoolSweeperEnabled() &&
properties.getTimeBetweenEvictionRunsMillis() > properties.getMaxAge()) {
log.warn(MessageFactory.get("connectionPool.timeBetweenEvictionRunsTooLarge", properties.getMaxAge()));
properties.setTimeBetweenEvictionRunsMillis((int) properties.getMaxAge());
}
}
/**
* Starts the pool cleaner (evicter) thread if pool sweeper is enabled. A cleaner that is still running from a
* previous start is terminated first, so this call never leaves a replaced task registered on the shared timer
* without a reference to stop it.
*
* @param properties the pool configuration containing sweeper settings
*/
public synchronized void initializePoolCleaner(PoolConfiguration properties) {
// If the evictor thread is supposed to run, start it now
if (properties.isPoolSweeperEnabled()) {
// Never overwrite a running cleaner: the replaced task would
// stay registered on the shared timer with no reference left to
// stop it, duplicating maintenance work for the pool's lifetime
terminatePoolCleaner();
poolCleaner = new PoolCleaner(this, properties.getTimeBetweenEvictionRunsMillis());
poolCleaner.start();
}
}
/**
* Stops the pool cleaner (evicter) thread if it is currently running and forgets its reference.
*/
public synchronized void terminatePoolCleaner() {
if (poolCleaner != null) {
poolCleaner.stopRunning();
poolCleaner = null;
}
}
/**
* thread safe way to abandon a connection signals a connection to be abandoned. this will disconnect the
* connection, and log the stack trace if logAbandoned=true
*
* @param con PooledConnection
*/
protected void abandon(PooledConnection con) {
if (con == null) {
return;
}
try {
con.lock();
String trace = con.getStackTrace();
if (getPoolProperties().isLogAbandoned()) {
log.warn(MessageFactory.get("connectionPool.connectionAbandoned", con, trace));
}
org.apache.tomcat.jdbc.pool.jmx.ConnectionPool jmx = jmxPool;
if (jmx != null) {
jmx.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.NOTIFY_ABANDON, trace);
}
// Release the connection
removeAbandonedCount.incrementAndGet();
release(con);
} finally {
con.unlock();
}
}
/**
* Thread safe way to suspect a connection. Similar to {@link #abandon(PooledConnection)}, but instead of actually
* abandoning the connection, this will log a warning and set the suspect flag on the {@link PooledConnection} if
* logAbandoned=true
*
* @param con PooledConnection
*/
protected void suspect(PooledConnection con) {
if (con == null) {
return;
}
if (con.isSuspect()) {
return;
}
try {
con.lock();
String trace = con.getStackTrace();
if (getPoolProperties().isLogAbandoned()) {
log.warn(MessageFactory.get("connectionPool.connectionSuspect",
con, (System.currentTimeMillis() - con.getTimestamp()), trace));
}
org.apache.tomcat.jdbc.pool.jmx.ConnectionPool jmx = jmxPool;
if (jmx != null) {
jmx.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.SUSPECT_ABANDONED_NOTIFICATION, trace);
}
con.setSuspect(true);
} finally {
con.unlock();
}
}
/**
* thread safe way to release a connection
*
* @param con PooledConnection
*/
protected void release(PooledConnection con) {
if (con == null) {
return;
}
boolean destroyed;
try {
con.lock();
destroyed = con.release();
if (destroyed) {
// Counter only decremented once
size.addAndGet(-1);
con.setHandler(null);
}
releasedCount.incrementAndGet();
} finally {
con.unlock();
}
// We've asynchronously reduced the number of connections
// we could have threads stuck in idle.poll(timeout) that will never be
// notified. Only a call that actually destroyed a connection opened a
// slot; offering a placeholder for a connection that was already
// released would run the pool over maxActive.
// A counted thread is not necessarily able to consume the placeholder:
// after an interrupted or timed out wait its queue latch is already
// deregistered while the wait count is still held, and a thread that
// creates its connection does not wait on the queue at all. With no
// counted thread able to consume it, a placeholder can only be taken
// by the shutdown drain, which drains this very queue: it would poll
// the placeholder, destroy it, and the release of that destruction
// would queue the next one, spinning for as long as any thread holds
// a wait count. A closed pool serves no more borrowers, so only
// queue the placeholder while it is open.
if (destroyed && !isClosed() && waitcount.get() > 0) {
if (!idle.offer(create(true))) {
log.warn(MessageFactory.get("connectionPool.failedToAddNewConnection"));
}
}
}
/**
* Thread safe way to retrieve a connection from the pool
*
* @param wait - time to wait, overrides the maxWait from the properties, set to -1 if you wish to use maxWait,
* 0 if you wish no wait time.
* @param username The user name to use for the connection
* @param password The password for the connection
*
* @return a connection
*
* @throws SQLException Failed to get a connection
*/
private PooledConnection borrowConnection(int wait, String username, String password) throws SQLException {
if (isClosed()) {
throw new SQLException(MessageFactory.get("connectionPool.poolClosed"));
}
// Get the current time stamp
long now = System.currentTimeMillis();
// See if there is one available immediately
PooledConnection con = idle.poll();
while (true) {
if (con != null) {
// Configure the connection and return it
PooledConnection result = borrowConnection(now, con, username, password);
if (result != null) {
return result;
}
// The connection was released while it sat idle, so this
// poll got nothing to hand out; keep polling. Nothing is
// counted here: a borrow is counted on the hand-off that
// succeeds, in setupConnection().
}
// If we get here, see if we need to create one
// this is not 100% accurate since it doesn't use a shared
// atomic variable - a connection can become idle while we are creating
// a new connection
if (size.get() < getPoolProperties().getMaxActive()) {
// Atomic duplicate check
if (size.addAndGet(1) > getPoolProperties().getMaxActive()) {
// If we got here, two threads passed through the first if
size.decrementAndGet();
} else {
// Create a connection, we're below the limit
return createConnection(now, con, username, password);
}
}
// Calculate wait time for this iteration
long maxWait = wait;
// If the passed in wait time is -1, means we should use the pool property value
if (wait == -1) {
maxWait = (getPoolProperties().getMaxWait() <= 0) ? Long.MAX_VALUE : getPoolProperties().getMaxWait();
}
long timetowait = Math.max(0, maxWait - (System.currentTimeMillis() - now));
waitcount.incrementAndGet();
try {
// A connection may have been destroyed between the size check
// above and the registration of this waiter; such a releasing
// thread saw no waiter and will not notify this poll, so
// re-check the limit now rather than wait for a notification
// that may never arrive. Skip the case where a connection (or
// a wake-up placeholder) is already queued to be taken.
if (idle.isEmpty() && size.get() < getPoolProperties().getMaxActive()) {
// Atomic duplicate check
if (size.addAndGet(1) > getPoolProperties().getMaxActive()) {
// If we got here, two threads passed through the first if
size.decrementAndGet();
} else {
// Create a connection, we're below the limit
return createConnection(now, con, username, password);
}
}
// Retrieve an existing connection
con = idle.poll(timetowait, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
if (getPoolProperties().getPropagateInterruptState()) {
Thread.currentThread().interrupt();
}
SQLException sx = new SQLException(MessageFactory.get("connectionPool.poolWaitInterrupted"));
sx.initCause(ex);
throw sx;
} finally {
waitcount.decrementAndGet();
}
if (maxWait == 0 && con == null) { // No wait, return one if we have one
org.apache.tomcat.jdbc.pool.jmx.ConnectionPool jmx = jmxPool;
if (jmx != null) {
jmx.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.POOL_EMPTY, "Pool empty - no wait.");
}
throw new PoolExhaustedException(MessageFactory.get("connectionPool.poolEmptyNoWait",
Thread.currentThread().getName(), busy.size()));
}
// We didn't get a connection, lets see if we timed out
if (con == null) {
if ((System.currentTimeMillis() - now) >= maxWait) {
org.apache.tomcat.jdbc.pool.jmx.ConnectionPool jmx = jmxPool;
if (jmx != null) {
jmx.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.POOL_EMPTY,
"Pool empty - timeout.");
}
throw new PoolExhaustedException(MessageFactory.get("connectionPool.poolEmptyTimeout",
Thread.currentThread().getName(), (maxWait / 1000), size.get(), busy.size(),
idle.size(), timetowait));
} else {
// No timeout, lets try again
continue;
}
}
}
}
/**
* Creates a JDBC connection and tries to connect to the database.
*
* @param now timestamp of when this was called
* @param notUsed Argument not used
* @param username The user name to use for the connection
* @param password The password for the connection
*
* @return a PooledConnection that has been connected
*
* @throws SQLException Failed to get a connection
*/
protected PooledConnection createConnection(long now, PooledConnection notUsed, String username, String password)
throws SQLException {
// No connections where available we'll create one
PooledConnection con = create(false);
if (username != null) {
con.getAttributes().put(PooledConnection.PROP_USER, username);
}
if (password != null) {
con.getAttributes().put(PooledConnection.PROP_PASSWORD, password);
}
boolean error = false;
try {
// Connect and validate the connection
con.lock();
con.connect();
if (isClosed()) {
// The pool was closed while this physical connection was
// being created; the shutdown drain has already passed over
// the queues, so neither hand the connection out nor register
// it in busy - the error path below disconnects it
throw new SQLException(MessageFactory.get("connectionPool.poolClosed"));
}
if (con.validate(PooledConnection.VALIDATE_INIT)) {
// No need to lock a new one, its not contented
con.setTimestamp(now);
if (getPoolProperties().isLogAbandoned()) {
con.setStackTraceElements(getThreadDumpElements());
}
if (!busy.offer(con)) {
log.debug("Connection doesn't fit into busy array, connection will not be traceable.");
}
createdCount.incrementAndGet();
return con;
} else {
// Validation failed, make sure we disconnect
// and clean up
throw new SQLException(MessageFactory.get("connectionPool.validationQueryFailed"));
}
} catch (Exception e) {
error = true;
if (log.isDebugEnabled()) {
log.debug("Unable to create a new JDBC connection.", e);
}
if (e instanceof SQLException) {
throw (SQLException) e;
} else {
SQLException ex = new SQLException(e.getMessage());
ex.initCause(e);
throw ex;
}
} finally {
// con can never be null here
if (error) {
release(con);
}
con.unlock();
}
}
/**
* Validates and configures a previously idle connection
*
* @param now - timestamp
* @param con - the connection to validate and configure
* @param username The user name to use for the connection
* @param password The password for the connection
*
* @return a connection
*
* @throws SQLException if a validation error happens
*/
protected PooledConnection borrowConnection(long now, PooledConnection con, String username, String password)
throws SQLException {
// We have a connection, lets set it up
// Flag to see if we need to nullify
boolean setToNull = false;
try {
con.lock();
if (con.isReleased()) {
return null;
}
// Evaluate username/password change as well as max age functionality
boolean forceReconnect = con.shouldForceReconnect(username, password) || con.isMaxAgeExpired();
if (!con.isDiscarded() && !con.isInitialized()) {
// Here it states that the connection not discarded, but the connection is null
// don't attempt a connect here. It will be done during the reconnect.
forceReconnect = true;
}
if (!forceReconnect) {
if ((!con.isDiscarded()) && con.validate(PooledConnection.VALIDATE_BORROW)) {
// Set the timestamp
con.setTimestamp(now);
if (getPoolProperties().isLogAbandoned()) {
// Set the stack trace for this pool
con.setStackTraceElements(getThreadDumpElements());
}
if (!busy.offer(con)) {
log.debug("Connection doesn't fit into busy array, connection will not be traceable.");
}
return con;
}
}
// If we reached here, that means the connection
// is either has another principal, is discarded or validation failed.
// We will make one more attempt
// in order to guarantee that the thread that just acquired
// the connection shouldn't have to poll again.
try {
con.reconnect();
reconnectedCount.incrementAndGet();
int validationMode = isInitNewConnections() ? PooledConnection.VALIDATE_INIT
: PooledConnection.VALIDATE_BORROW;
if (con.validate(validationMode)) {
// Set the timestamp
con.setTimestamp(now);
if (getPoolProperties().isLogAbandoned()) {
// Set the stack trace for this pool
con.setStackTraceElements(getThreadDumpElements());
}
if (!busy.offer(con)) {
log.debug("Connection doesn't fit into busy array, connection will not be traceable.");
}
return con;
} else {
// Validation failed.
throw new SQLException(MessageFactory.get("connectionPool.failedToValidateNewConnection"));
}
} catch (Exception x) {
release(con);
setToNull = true;
if (x instanceof SQLException) {
throw (SQLException) x;
} else {
SQLException ex = new SQLException(x.getMessage());
ex.initCause(x);
throw ex;
}
}
} finally {
con.unlock();
if (setToNull) {
con = null;
}
}
}
/**
* Returns whether new connections should be initialized by invoking {@link PooledConnection#validate(int)} with
* {@link PooledConnection#VALIDATE_INIT}.
*
* @return true if pool is either configured to test connections on connect or a non-NULL init SQL has been
* configured
*/
private boolean isInitNewConnections() {
return getPoolProperties().isTestOnConnect() || getPoolProperties().getInitSQL() != null;
}
/**
* Terminate the current transaction for the given connection.
*
* @param con The connection
*
* @return <code>true</code> if the connection TX termination succeeded otherwise <code>false</code>
*/
protected boolean terminateTransaction(PooledConnection con) {
try {
// Terminate the transaction of any connection that is returned
// while still in one; the pool-level defaultAutoCommit property
// must not gate this, the connection's own state is what matters
boolean doRollback = getPoolProperties().getRollbackOnReturn();
boolean doCommit = !doRollback && getPoolProperties().getCommitOnReturn();
if (doRollback || doCommit) {
Connection raw = con.getConnection();
if (raw == null) {
// Defensive: the pool's call path reaches here only on
// a session that is not closed; with nothing to
// terminate there is nothing to fail, and the caller's
// own closed checks take the connection out of service
return true;
}
if (!raw.getAutoCommit()) {
if (doRollback) {
raw.rollback();
} else {
raw.commit();
}
}
}
return true;
} catch (SQLException x) {
log.warn(MessageFactory.get("connectionPool.unableToTerminateTransaction"), x);
return false;
}
}
/**
* Determines if a connection should be closed upon return to the pool.
*
* @param con - the connection
* @param action - the validation action that should be performed
*
* @return <code>true</code> if the connection should be closed
*/
protected boolean shouldClose(PooledConnection con, int action) {
if (con.getConnectionVersion() < getPoolVersion()) {
return true;
}
if (con.isDiscarded()) {
return true;
}
try {
if (con.isClosed()) {
return true;
}
} catch (SQLException e) {
log.warn(MessageFactory.get("connectionPool.unableToCheckClosed"), e);
return true;
}
if (isClosed()) {
return true;
}
// Terminate the transaction before validating. When the pool is
// configured to commit or roll back on return, that is the explicit
// choice for the pending work, and a validation that runs first
// would end the transaction with its own rollback and silently
// override a configured commitOnReturn. With the termination first,
// a validation below only ever sees a transaction the return path
// deliberately left open.
if (!terminateTransaction(con)) {
return true;
}
if (!con.validate(action)) {
return true;
}
return false;
}
/**
* Checks whether this connection has {@link PooledConnection#isMaxAgeExpired() expired} and tries to reconnect if
* it has.
*
* @param con PooledConnection
*
* @return true if the connection was either not expired or expired but reconnecting succeeded, false if
* reconnecting failed (either because a new connection could not be established or validating the newly
* created connection failed)
*
* @see PooledConnection#isMaxAgeExpired()
*/
protected boolean reconnectIfExpired(PooledConnection con) {
if (con.isMaxAgeExpired()) {
try {
if (log.isDebugEnabled()) {
log.debug("Connection [" + this + "] expired because of maxAge, trying to reconnect");
}
con.reconnect();
reconnectedCount.incrementAndGet();
if (isInitNewConnections() && !con.validate(PooledConnection.VALIDATE_INIT)) {
return false;
}
} catch (Exception e) {
log.error(MessageFactory.get("connectionPool.failedToReconnectExpired", this), e);
return false;
}
}
return true;
}
/**
* Returns a connection to the pool If the pool is closed, the connection will be released If the connection is not
* part of the busy queue, it will be released. If {@link PoolProperties#testOnReturn} is set to true it will be
* validated
*
* @param con PooledConnection to be returned to the pool
*/
protected void returnConnection(PooledConnection con) {
if (isClosed()) {
// If the connection pool is closed
// close the connection instead of returning it
release(con);
return;
}
if (con != null) {
try {
returnedCount.incrementAndGet();
con.lock();
if (con.isSuspect()) {
if (poolProperties.isLogAbandoned() && log.isInfoEnabled()) {
log.info(MessageFactory.get("connectionPool.suspectConnectionReturned",
con, (System.currentTimeMillis() - con.getTimestamp())));
}
org.apache.tomcat.jdbc.pool.jmx.ConnectionPool jmx = jmxPool;
if (jmx != null) {
jmx.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.SUSPECT_RETURNED_NOTIFICATION,
"Connection(" + con + ") that has been marked suspect was returned.");
}
}
if (busy.remove(con)) {
try {
if (!shouldClose(con, PooledConnection.VALIDATE_RETURN) && reconnectIfExpired(con)) {
con.clearWarnings();
con.setStackTrace(null);
con.setTimestamp(System.currentTimeMillis());
// The application is done with the connection:
// close the JDBC 4.3 request that beginRequest()
// opened in setupConnection(). A connection
// discarded instead of going back to the pool is
// not hinted; it is about to be closed, which
// subsumes the hint.
invokeRequestHint(END_REQUEST, con.getConnection());
if (((idle.size() >= poolProperties.getMaxIdle()) && !poolProperties.isPoolSweeperEnabled()) ||
(!idle.offer(con))) {
if (log.isDebugEnabled()) {
log.debug("Connection [" + con +
"] will be closed and not returned to the pool, idle[" + idle.size() +
"]>=maxIdle[" + poolProperties.getMaxIdle() + "] idle.offer failed.");
}
release(con);
}
} else {
if (log.isDebugEnabled()) {
log.debug("Connection [" + con + "] will be closed and not returned to the pool.");
}
release(con);
}
} catch (RuntimeException x) {
// Everything above runs after the connection was
// claimed from the busy queue and calls into the
// driver - a closed check, a transaction termination,
// a validation, a warnings clear - before the
// connection is handed to the idle queue or released.
// A driver that fails one of those calls with an
// unchecked exception must not strand the connection
// between the two queues: it would be invisible to
// checkAbandoned(), to checkIdle() and to the close()
// drain alike, never disconnected, and the pool would
// permanently lose one maxActive slot per occurrence.
// Take it out of service instead, as a failed
// validation already does: the application sees the
// return complete quietly, just as it does when the
// driver fails a check it does handle. Release
// before logging: nothing but the release may keep a
// claimed connection from being closed, and after it
// the connection renders itself without consulting
// the driver that failed.
con.setDiscarded(true);
release(con);
log.warn(MessageFactory.get("connectionPool.unableToReturnConnection", con), x);
}
} else {
if (log.isDebugEnabled()) {
log.debug("Connection [" + con +
"] will be closed and not returned to the pool, busy.remove failed.");
}
release(con);
}
} finally {
con.unlock();
}
}
}
/**
* Determines if a connection should be abandoned based on {@link PoolProperties#abandonWhenPercentageFull} setting.
*
* @return <code>true</code> if the connection should be abandoned
*/
protected boolean shouldAbandon() {
if (!poolProperties.isRemoveAbandoned()) {
return false;
}
if (poolProperties.getAbandonWhenPercentageFull() == 0) {
return true;
}
float used = busy.size();
float max = poolProperties.getMaxActive();
float perc = poolProperties.getAbandonWhenPercentageFull();
return (used / max * 100f) >= perc;
}
/**
* Iterates through all the busy connections and checks for connections that have timed out
*/
public void checkAbandoned() {
try {
if (busy.isEmpty()) {
return;
}
Iterator<PooledConnection> locked = busy.iterator();
int sto = getPoolProperties().getSuspectTimeout();
while (locked.hasNext()) {
PooledConnection con = locked.next();
boolean setToNull = false;
try {
con.lock();
// The con has been returned to the pool or released
// ignore it
if (idle.contains(con) || con.isReleased()) {
continue;
}
long time = con.getTimestamp();
long now = System.currentTimeMillis();
if (shouldAbandon() && (now - time) > con.getAbandonTimeout()) {
// Claim the connection before acting on it; without
// a real lock the checks above may already be stale,
// and a connection the application returned to the
// pool in the meantime must not be abandoned
if (!busy.remove(con)) {
continue;
}
abandon(con);
setToNull = true;
} else if (sto > 0 && (now - time) > (sto * 1000L)) {
suspect(con);
} else {
// Do nothing
}
} finally {
con.unlock();
if (setToNull) {
con = null;
}
}
}
} catch (ConcurrentModificationException e) {
if (log.isDebugEnabled()) {
log.debug("checkAbandoned failed.", e);
}
} catch (Exception e) {
log.warn(MessageFactory.get("connectionPool.checkAbandonedFailed"), e);
}
}
/**
* Iterates through the idle connections and resizes the idle pool based on parameters
* {@link PoolProperties#maxIdle}, {@link PoolProperties#minIdle}, {@link PoolProperties#minEvictableIdleTimeMillis}
*/
public void checkIdle() {
checkIdle(false);
}
/**
* Iterates through the idle connections and resizes the idle pool based on pool configuration parameters. If
* ignoreMinSize is true, idle connections may be removed even if the pool is below minIdle.
*
* @param ignoreMinSize if true, ignore the minIdle setting when evicting
*/
public void checkIdle(boolean ignoreMinSize) {
try {
if (idle.isEmpty()) {
return;
}
long now = System.currentTimeMillis();
Iterator<PooledConnection> unlocked = idle.iterator();
while ((ignoreMinSize || (idle.size() >= getPoolProperties().getMinIdle())) && unlocked.hasNext()) {
PooledConnection con = unlocked.next();
boolean setToNull = false;
try {
con.lock();
// The con been taken out, we can't clean it up
if (busy.contains(con)) {
continue;
}
long time = con.getTimestamp();
if (shouldReleaseIdle(now, con, time)) {
// Claim the connection before acting on it; without
// a real lock the checks above may already be stale,
// and a connection a borrower took out of the idle
// queue in the meantime must not be released here
if (!idle.remove(con)) {
continue;
}
releasedIdleCount.incrementAndGet();
release(con);
setToNull = true;
} else {
// Do nothing
}
} finally {
con.unlock();
if (setToNull) {
con = null;
}
}
}
} catch (ConcurrentModificationException e) {
if (log.isDebugEnabled()) {
log.debug("checkIdle failed.", e);
}
} catch (Exception e) {
log.warn(MessageFactory.get("connectionPool.checkIdleFailed"), e);
}
}
/**
* Determines whether an idle connection should be released from the pool.
*
* @param now the current time in milliseconds
* @param con the pooled connection to evaluate
* @param time the timestamp when the connection last became idle
*
* @return {@code true} if the connection should be released
*/
protected boolean shouldReleaseIdle(long now, PooledConnection con, long time) {
if (con.getConnectionVersion() < getPoolVersion()) {
return true;
} else {
return (con.getReleaseTime() > 0) && ((now - time) > con.getReleaseTime()) &&
(getSize() > getPoolProperties().getMinIdle());
}
}
/**
* Forces a validation of all idle connections if {@link PoolProperties#testWhileIdle} is set.
* <p>
* Note: when this method is invoked externally while the per-connection lock is disabled (sweeper switched off and
* {@link PoolProperties#getUseLock()} returning {@code false}), it still serialises with borrowers through the
* queues: every connection is claimed out of the idle queue before it is validated or reconnected, and is put back
* or released afterwards, so a borrower is never handed a connection this method is working on. That claim is what
* makes an invocation concurrent with traffic safe, as {@link PooledConnection#lock()} alone protects nothing in
* this configuration. A pool closed while a connection is claimed does not get it offered back into its idle
* queue - the shutdown drain has passed it by, so the released connection is disconnected directly; the small
* window that accepts between that check and the queue hand-over is the same one the return path accepts.
*/
public void testAllIdle() {
testAllIdle(false);
}
/**
* Forces a validation of all idle connections if {@link PoolProperties#testWhileIdle} is set.
*
* @param checkMaxAgeOnly whether to only check {@link PooledConnection#isMaxAgeExpired()} but not invoke
* {@link PooledConnection#validate(int)}
*/
public void testAllIdle(boolean checkMaxAgeOnly) {
try {
if (idle.isEmpty()) {
return;
}
Iterator<PooledConnection> unlocked = idle.iterator();
while (unlocked.hasNext()) {
PooledConnection con = unlocked.next();
try {
con.lock();
// The con been taken out, we can't clean it up
if (busy.contains(con)) {
continue;
}
// Claim the connection before acting on it; without a
// real lock the check above may already be stale, and a
// connection a borrower took out of the idle queue in
// the meantime must not be validated or reconnected
// here. Reconnecting one under a borrower is the worst
// of it: both threads run disconnect-then-connect on the
// same PooledConnection, the loser's physical handle is
// overwritten by the winner's field write, and a session
// nothing references any more can never be closed.
if (!idle.remove(con)) {
continue;
}
boolean keep;
try {
if (checkMaxAgeOnly) {
keep = reconnectIfExpired(con);
} else {
keep = reconnectIfExpired(con) && con.validate(PooledConnection.VALIDATE_IDLE);
}
} catch (RuntimeException x) {
// The checks above swallow every checked driver
// failure, but an unchecked throw from behind one of
// them - a validation ending a transaction is one
// such call - must not escape now that the
// connection is claimed: unwinding here would leave
// it in neither queue, invisible to the next sweep
// and to the close() drain alike. Take it out of
// service instead, as a failed check already does.
con.setDiscarded(true);
release(con);
log.warn(MessageFactory.get("connectionPool.unableToTestIdleConnection", con), x);
continue;
}
if (keep) {
if (isClosed()) {
// The pool was closed while this connection was
// claimed: the shutdown drain has already passed
// over the queues and a closing pool offers no
// further item to the idle queue, so return it
// through the drain's own exit, as the return
// path does - an offer would land it in a dead
// queue that nothing drains any more, stranding
// the session out of every counter
release(con);
} else if (((idle.size() >= poolProperties.getMaxIdle()) &&
!poolProperties.isPoolSweeperEnabled()) || (!idle.offer(con))) {
if (log.isDebugEnabled()) {
log.debug("Connection [" + con + "] passed the idle test but will be closed and " +
"not returned to the pool, idle[" + idle.size() + "]>=maxIdle[" +
poolProperties.getMaxIdle() + "] idle.offer failed.");
}
release(con);
}
} else {
releasedIdleCount.incrementAndGet();
release(con);
}
} finally {
con.unlock();
}
}
} catch (ConcurrentModificationException e) {
if (log.isDebugEnabled()) {
log.debug("testAllIdle failed.", e);
}
} catch (Exception e) {
log.warn(MessageFactory.get("connectionPool.testAllIdleFailed"), e);
}
}
/**
* Creates a stack trace representing the existing thread's current state.
*
* @return a string object representing the current state.
*/
protected static String getThreadDump() {
Exception x = new Exception();
x.fillInStackTrace();
return getStackTrace(x);
}
/**
* Captures the existing thread's current state without formatting it. An abandoned connection reports the borrow
* site only rarely, so the formatting is left to {@link PooledConnection#getStackTrace()} and a borrow that is
* never reported never pays for a thread dump.
*
* @return the stack trace elements representing the current state
*/
protected static StackTraceElement[] getThreadDumpElements() {
return new Exception().getStackTrace();
}
/**
* Convert an exception into a String
*
* @param x - the throwable
*
* @return a string representing the stack trace
*/
public static String getStackTrace(Throwable x) {
if (x == null) {
return null;
} else {
java.io.ByteArrayOutputStream bout = new java.io.ByteArrayOutputStream();
java.io.PrintStream writer = new java.io.PrintStream(bout);
x.printStackTrace(writer);
String result = bout.toString();
return (x.getMessage() != null && x.getMessage().length() > 0) ? x.getMessage() + ";" + result : result;
}
}
/**
* Create a new pooled connection object. Not connected nor validated.
*
* @param incrementCounter <code>true</code> to increment the connection count
*
* @return a pooled connection object
*/
protected PooledConnection create(boolean incrementCounter) {
if (incrementCounter) {
size.incrementAndGet();
}
PooledConnection con = new PooledConnection(getPoolProperties(), this);
return con;
}
/**
* Purges all connections in the pool. For connections currently in use, these connections will be purged when
* returned on the pool. This call also purges connections that are idle and in the pool To only purge used/active
* connections see {@link #purgeOnReturn()}
*/
public void purge() {
purgeOnReturn();
checkIdle(true);
}
/**
* Purges connections when they are returned from the pool. This call does not purge idle connections until they are
* used. To purge idle connections see {@link #purge()}
*/
public void purgeOnReturn() {
poolVersion.incrementAndGet();
}
/**
* Hook to perform final actions on a pooled connection object once it has been disconnected and will be discarded
*
* @param con The connection
*/
protected void finalize(PooledConnection con) {
JdbcInterceptor handler = con.getHandler();
while (handler != null) {
// Read the next link before the callback, as the callback
// is allowed to modify it
JdbcInterceptor next = handler.getNext();
try {
handler.reset(null, null);
} catch (Throwable t) {
// One broken interceptor must not stop the rest of the
// chain from being reset
log.warn(MessageFactory.get("connectionPool.interceptorResetFailed",
handler.getClass().getName()), t);
}
handler = next;
}
}
/**
* Hook to perform final actions on a pooled connection object once it has been disconnected and will be discarded
*
* @param con The connection
* @param finalizing <code>true</code> if finalizing the connection
*/
protected void disconnectEvent(PooledConnection con, boolean finalizing) {
JdbcInterceptor handler = con.getHandler();
while (handler != null) {
// Read the next link before the callback, as the callback
// is allowed to modify it
JdbcInterceptor next = handler.getNext();
try {
handler.disconnected(this, con, finalizing);
} catch (Throwable t) {
// One broken interceptor must not stop the rest of the
// chain from being notified
log.warn(MessageFactory.get("connectionPool.interceptorDisconnectedFailed",
handler.getClass().getName()), t);
}
handler = next;
}
}
/**
* Return the object that is potentially registered in JMX for notifications
*
* @return the object implementing the {@link org.apache.tomcat.jdbc.pool.jmx.ConnectionPoolMBean} interface
*/
public org.apache.tomcat.jdbc.pool.jmx.ConnectionPool getJmxPool() {
return jmxPool;
}
/**
* Create MBean object that can be registered.
*/
protected void createMBean() {
try {
jmxPool = new org.apache.tomcat.jdbc.pool.jmx.ConnectionPool(this);
} catch (Exception x) {
log.warn(MessageFactory.get("connectionPool.unableToStartJmx", getName()), x);
}
}
/**
* Publishes the MBeans of all connections this pool currently holds, below the pool's own MBean. A connection
* registers itself when its physical connection is established, but that is only possible once the pool's own
* MBean has been published - and a container publishes it only after the pool, already connected to its initial
* size, has been created. Without this sweep the connections made before that publication, and in particular the
* ones the pool pre-created for its initial size, would be counted in the pool's size while staying invisible to
* JMX for as long as they are never borrowed.
*/
public void registerConnectionMBeans() {
// The connections publish themselves below the pool's own name, so
// until that name is registered there is no base to publish them under.
org.apache.tomcat.jdbc.pool.jmx.ConnectionPool jmx = jmxPool;
if (jmx == null || jmx.getObjectName() == null) {
return;
}
registerConnectionMBeans(idle);
registerConnectionMBeans(busy);
}
private void registerConnectionMBeans(BlockingQueue<PooledConnection> pool) {
// The fair queue iterates a snapshot taken under its lock, the default
// queue a view that tolerates concurrent modification: either way, a
// connection that is concurrently taken out of or added to the queue
// may or may not be seen, and both answers are correct - the one this
// pass misses registers itself on the borrow that hands it out.
for (PooledConnection con : pool) {
try {
// Under the connection's own lock, as every operation acting
// on a pooled connection is: the registration must not slip in
// beside the release of the same connection, whose
// unregistration would then find no name to take back and this
// one would leave a MBean of a dead connection behind forever.
con.lock();
// A connection that is still without a physical connection is a
// placeholder for a slot, not a session to monitor, and it
// registers through the connect path once it gets its
// connection - publishing one now would name a session that
// does not exist.
if (con.isInitialized()) {
con.createMBean();
}
} finally {
con.unlock();
}
}
}
/**
* The total number of connections borrowed from this pool.
*
* @return the borrowed connection count
*/
public long getBorrowedCount() {
return borrowedCount.get();
}
/**
* The total number of connections returned to this pool.
*
* @return the returned connection count
*/
public long getReturnedCount() {
return returnedCount.get();
}
/**
* The total number of connections created by this pool.
*
* @return the created connection count
*/
public long getCreatedCount() {
return createdCount.get();
}
/**
* The total number of connections released from this pool.
*
* @return the released connection count
*/
public long getReleasedCount() {
return releasedCount.get();
}
/**
* The total number of connections reconnected by this pool.
*
* @return the reconnected connection count
*/
public long getReconnectedCount() {
return reconnectedCount.get();
}
/**
* The total number of connections released by remove abandoned.
*
* @return the PoolCleaner removed abandoned connection count
*/
public long getRemoveAbandonedCount() {
return removeAbandonedCount.get();
}
/**
* The total number of connections released by eviction.
*
* @return the PoolCleaner evicted idle connection count
*/
public long getReleasedIdleCount() {
return releasedIdleCount.get();
}
/**
* reset the statistics of this pool.
*/
public void resetStats() {
borrowedCount.set(0);
returnedCount.set(0);
createdCount.set(0);
releasedCount.set(0);
reconnectedCount.set(0);
removeAbandonedCount.set(0);
releasedIdleCount.set(0);
}
/**
* Thread safe wrapper around a future for the regular queue This one retrieves the pooled connection object and
* performs the initialization according to interceptors and validation rules. This class is thread safe and is
* cancellable
*/
protected class ConnectionFuture implements Future<Connection>, Runnable {
Future<PooledConnection> pcFuture = null;
/**
* The claim on the pooled connection this future produces. The queue hands its connection to every caller of
* the queue future, so this single compare-and-set is what decides who may act on it: a
* {@link #get(long, TimeUnit)} caller, who then owns the connection that comes out of it, or
* {@link #cancel(boolean)}, whose cleanup task returns that connection to the pool. Whichever path loses the
* claim must neither use the connection nor report a cancellation of it, so one physical connection never ends
* up with two owners.
*/
AtomicBoolean configured = new AtomicBoolean(false);
CountDownLatch latch = new CountDownLatch(1);
volatile Connection result = null;
SQLException cause = null;
AtomicBoolean cancelled = new AtomicBoolean(false);
volatile PooledConnection pc = null;
/**
* Creates a ConnectionFuture backed by an async pooled connection future.
*
* @param pcf the future for the pooled connection
*/
public ConnectionFuture(Future<PooledConnection> pcf) {
this.pcFuture = pcf;
}
/**
* Creates a ConnectionFuture backed by an already available pooled connection.
*
* @param pc the pooled connection
*
* @throws SQLException if connection setup fails
*/
public ConnectionFuture(PooledConnection pc) throws SQLException {
this.pc = pc;
result = ConnectionPool.this.setupConnection(pc);
configured.set(true);
}
/**
* {@inheritDoc}
*/
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
// On the async path the pc field is never assigned; once a
// connection has been produced it is held in the result field,
// so a non-null result means the future is complete and it is
// too late to cancel. A queue future the pool closure already
// cancelled is complete in the same way and leaves nothing to
// deregister and no connection to clean up, so report nothing
// cancelled without arming the deferred cleanup task, which a
// java.util.concurrent.Future must not run for a completed task.
if (pc != null || result != null || cancelled.get() || pcFuture.isCancelled()) {
return false;
}
// The queue may have produced its connection already, and a get()
// caller that received it is using it, so cancel only when this
// thread wins the claim on that connection. Losing the claim means
// the connection belongs to that caller, which is the completed
// case a java.util.concurrent.Future reports as not cancelled.
if (!configured.compareAndSet(false, true)) {
return false;
}
cancelled.set(true);
if (pcFuture.cancel(mayInterruptIfRunning)) {
// The waiter was still queued, so it is deregistered and no
// connection will be produced for this future. Release the
// callers that are waiting on the claim above, as nobody is
// left to publish a result for them.
latch.countDown();
} else {
// The pooled connection was already handed over to the queue
// future (or is being handed over right now), so cancel by
// retrieving the connection and returning it to the pool
ConnectionPool.this.cancellator.execute(this);
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public Connection get() throws InterruptedException, ExecutionException {
try {
return get(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
} catch (TimeoutException x) {
throw new ExecutionException(x);
}
}
/**
* {@inheritDoc}
*/
@Override
public Connection get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
PooledConnection pc = this.pc != null ? this.pc : pcFuture.get(timeout, unit);
if (pc != null) {
if (result != null) {
return result;
}
if (configured.compareAndSet(false, true)) {
configure(pc);
} else {
// If we reach here, another thread claimed the connection:
// a get() caller that is setting it up, or the cancellation
// cleanup that returns it to the pool
if (!latch.await(timeout, unit)) { // This shouldn't block for long
if (cancelled.get()) {
// A cancellation won the claim, so its cleanup is
// what this wait was for: as for a cancelled
// java.util.concurrent.Future, its callers are told
// about the cancellation rather than about their
// own deadline expiring. A timeout observed before
// the cancellation wins the claim stays a timeout;
// that window is inherent and bounded, the next
// get() here observes the cancellation
throw new CancellationException();
}
throw new TimeoutException();
}
}
if (result == null) {
if (cancelled.get()) {
// The cancellation cleanup won the claim on this
// connection and returned it to the pool, so it is not
// ours to hand out; the callers of a cancelled future
// are told about the cancellation instead of being
// handed a connection that is already back in the pool
throw new CancellationException();
}
throw new ExecutionException(cause);
}
return result;
} else {
return null;
}
}
/**
* Sets up the pooled connection that the claim on {@link #configured} gave to this caller and publishes what
* came out of it for the callers waiting on {@link #latch}. Only the winner of that claim may call this, which
* is what keeps the connection from being handed to one caller while the cancellation cleanup returns it to the
* pool.
*
* @param pooled the pooled connection the queue handed out
*/
private void configure(PooledConnection pooled) {
try {
PooledConnection pc = borrowConnection(System.currentTimeMillis(), pooled, null, null);
if (pc != null) {
result = ConnectionPool.this.setupConnection(pc);
} else {
// The connection was released while it sat idle, so this
// hand-off got nothing to set up. The synchronous borrow
// treats that as a poll to retry; an async poll is served
// by exactly the one connection its wait produced, so it
// fails the caller instead. Every caller released on the
// latch below reports what this publishes, and an
// ExecutionException with no cause tells them nothing.
cause = new SQLException(MessageFactory.get("connectionPool.asyncBorrowConnectionReleased"));
}
} catch (SQLException x) {
cause = x;
} finally {
latch.countDown();
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean isCancelled() {
return pc == null && (pcFuture.isCancelled() || cancelled.get());
}
/**
* {@inheritDoc}
*/
@Override
public boolean isDone() {
return pc != null || pcFuture.isDone();
}
/**
* run method to be executed when cancelled by an executor
*/
@Override
public void run() {
// cancel() armed this task because it won the claim on the
// connection this future produces, so this is the path that hands
// that connection back to the pool when no get() caller claimed it.
// It sets the connection up the way a borrow does, so that the
// return runs through the interceptors, but it never publishes a
// result: a get() caller waiting on the claim has to observe the
// cancellation rather than a connection that is already closed.
try {
PooledConnection pooled = null;
try {
pooled = pcFuture.get(); // The connection this future produced
} catch (InterruptedException x) {
Thread.currentThread().interrupt();
} catch (ExecutionException x) {
// The queue future produced no connection to return
} catch (CancellationException x) {
// The pool closure cancelled the queued wait between the
// registration of this cleanup task and its execution; this
// future never produced a connection, so there is nothing
// to close
}
if (pooled != null) {
try {
PooledConnection pc = borrowConnection(System.currentTimeMillis(), pooled, null, null);
if (pc != null) {
Connection con = ConnectionPool.this.setupConnection(pc);
if (con != null) {
con.close(); // Return to the pool
}
}
} catch (SQLException x) {
// The connection was released or could not be set up,
// so there is nothing left to return to the pool
cause = x;
}
}
} catch (Exception x) {
log.error(MessageFactory.get("connectionPool.unableToCancelConnectionFuture"), x);
} finally {
// Whatever came out of it, the callers waiting on the claim are
// done waiting
latch.countDown();
}
}
}
private static volatile Timer poolCleanTimer = null;
private static Set<PoolCleaner> cleaners = new HashSet<>();
private static synchronized void registerCleaner(PoolCleaner cleaner) {
unregisterCleaner(cleaner);
cleaners.add(cleaner);
if (poolCleanTimer == null) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(ConnectionPool.class.getClassLoader());
// Create the timer thread in a PrivilegedAction so that a
// reference to the web application class loader is not created
// via Thread.inheritedAccessControlContext
PrivilegedAction<Timer> pa = new PrivilegedNewTimer();
poolCleanTimer = AccessController.doPrivileged(pa);
} finally {
Thread.currentThread().setContextClassLoader(loader);
}
}
poolCleanTimer.schedule(cleaner, cleaner.sleepTime, cleaner.sleepTime);
}
private static synchronized void unregisterCleaner(PoolCleaner cleaner) {
boolean removed = cleaners.remove(cleaner);
if (removed) {
cleaner.cancel();
if (poolCleanTimer != null) {
poolCleanTimer.purge();
if (cleaners.isEmpty()) {
poolCleanTimer.cancel();
poolCleanTimer = null;
}
}
}
}
private static class PrivilegedNewTimer implements PrivilegedAction<Timer> {
@Override
public Timer run() {
return new Timer("Tomcat JDBC Pool Cleaner["+ System.identityHashCode(ConnectionPool.class.getClassLoader()) + ":"+
System.currentTimeMillis() + "]", true);
}
}
// Testing use only
/**
* Returns an unmodifiable set of all registered pool cleaner tasks.
*
* @return the set of pool cleaner timer tasks
*/
public static synchronized Set<TimerTask> getPoolCleaners() {
return Collections.<TimerTask>unmodifiableSet(cleaners);
}
/**
* Returns the current pool version counter. This counter is incremented when connections are purged, allowing
* pooled connections to detect that they should be discarded.
*
* @return the current pool version
*/
public long getPoolVersion() {
return poolVersion.get();
}
// Testing use only
/**
* Returns the shared timer used for scheduling pool cleaner tasks.
*
* @return the pool cleaner timer, or {@code null} if no cleaners are registered
*/
public static synchronized Timer getPoolTimer() {
return poolCleanTimer;
}
/**
* A scheduled task that periodically checks for abandoned connections, validates idle connections, and resizes the
* idle pool according to the pool's configuration.
*/
protected static class PoolCleaner extends TimerTask {
/**
* Weak reference to the connection pool being monitored.
*/
protected WeakReference<ConnectionPool> pool;
/**
* Sleep interval in milliseconds between cleaner runs.
*/
protected long sleepTime;
/**
* Creates a new pool cleaner task.
*
* @param pool the connection pool to clean
* @param sleepTime the interval in milliseconds between cleanups
*/
PoolCleaner(ConnectionPool pool, long sleepTime) {
this.pool = new WeakReference<>(pool);
this.sleepTime = sleepTime;
if (sleepTime <= 0) {
log.warn(MessageFactory.get("connectionPool.evicterIntervalZero"));
this.sleepTime = 1000 * 30;
} else if (sleepTime < 1000) {
log.warn(MessageFactory.get("connectionPool.evicterIntervalTooLow"));
}
}
@Override
public void run() {
ConnectionPool pool = this.pool.get();
if (pool == null) {
stopRunning();
} else if (!pool.isClosed()) {
try {
if (pool.getPoolProperties().isRemoveAbandoned() ||
pool.getPoolProperties().getSuspectTimeout() > 0) {
pool.checkAbandoned();
}
// The eviction predicate in checkIdle() compares
// against the total pool size, not the idle count: a
// busy pool can be over the min-idle floor while idle
// stays at or below it
if (pool.getPoolProperties().getMinIdle() < pool.getSize()) {
pool.checkIdle();
}
if (pool.getPoolProperties().isTestWhileIdle()) {
pool.testAllIdle(false);
} else if (pool.getPoolProperties().getMaxAge() > 0) {
pool.testAllIdle(true);
}
} catch (Exception x) {
log.error(x.toString(), x);
}
}
}
/**
* Starts the pool cleaner by registering it with the shared timer.
*/
public void start() {
registerCleaner(this);
}
/**
* Stops the pool cleaner by unregistering it from the shared timer.
*/
public void stopRunning() {
unregisterCleaner(this);
}
}
}