com.senzing.sql.ConnectionPool Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of senzing-commons Show documentation
Show all versions of senzing-commons Show documentation
Utility classes and functions common to multiple Senzing projects.
The newest version!
package com.senzing.sql;
import com.senzing.util.Quantified;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import java.io.StringWriter;
import java.io.PrintWriter;
import static com.senzing.sql.ConnectionPool.Stat.*;
/**
* Provides a pool for JDBC {@link Connection} instances. The pool can
* be a fixed size or may have a maximum and minimum size that it may
* grow and shrink between. Additionally, each {@link Connection} can
* have a maximum lifespan and a maximum number of uses before being
* replaced with a new {@link Connection}.
*/
public class ConnectionPool implements Quantified {
/**
* Constant for converting between nanoseconds and milliseconds.
*/
private static final long ONE_MILLION = 1000000L;
/**
* The amount of time to wait before forcing to wakeup and checking the
* pool state.
*/
private static final long WAIT_TIMEOUT = 2000L;
/**
* Encapsualtes and describes a pooled database connection.
*/
protected static class PooledConnection {
/**
* The associated {@link Connection}.
*/
private Connection connection = null;
/**
* The time that the {@link Connection} was opened.
*/
private long createdTimeNanos;
/**
* The number of times the connection has been leased.
*/
private int leaseCount = 0;
/**
* The {@link PooledConnectionHandler} for the current lease. This
* is null
if not currently leased.
*/
private PooledConnectionHandler currentLeaseHandler = null;
/**
* Constructs with the specified {@link Connection}.
*
* @param connection The {@link Connection} with which to construct.
*/
public PooledConnection(Connection connection) {
this.connection = connection;
this.createdTimeNanos = System.nanoTime();
this.leaseCount = 0;
this.currentLeaseHandler = null;
}
/**
* Gets the associated {@link Connection}.
*
* @return The associated {@link Connection}.
*/
public Connection getConnection() {
return this.connection;
}
/**
* Gets the nanosecond creation timestamp.
*
* @return The nanosecond creation timestamp.
*/
public long getCreatedTimeNanos() {
return this.createdTimeNanos;
}
/**
* Gets the lifespan (in milliseconds) of the associated {@link Connection}
* thus far.
*
* @return The lifespan (in milliseconds) of the associated
* {@link Connection} thus far.
*/
public long getLifespan() {
return (System.nanoTime() - this.createdTimeNanos) / ONE_MILLION;
}
/**
* Gets the {@link PooledConnectionHandler} for the current lease. This
* returns null
if not currently leased.
*
* @return The {@link PooledConnectionHandler} for the current lease, or
* null
if not currently leased.
*/
public PooledConnectionHandler getCurrentLeaseHandler() {
return this.currentLeaseHandler;
}
/**
* Sets the {@link PooledConnectionHandler} for the current lease.
*
* @param handler The {@link PooledConnectionHandler} for the current lease,
* or null
if not leased.
*/
public void setCurrentLeaseHandler(PooledConnectionHandler handler) {
if (handler != null && this.currentLeaseHandler != null) {
throw new IllegalStateException(
"Setting a connection handler on a pooled connection that already "
+ "has one set (i.e.: is already or still leased)");
}
// set the handler
this.currentLeaseHandler = handler;
// check if leasing
if (handler != null) {
this.leaseCount++;
}
}
/**
* Returns the number of times the {@link Connection} has been leased.
*
* @return The number of times the {@link Connection} has been leased.
*/
public int getLeaseCount() {
return this.leaseCount;
}
}
/**
* A background thread that will handle expiring available connections when
* the {@link ConnectionPool} is idle. This {@link Thread} does not do
* anything when the {@link ConnectionPool} is actively providing new leases
* on connections.
*/
private class ConnectionExpireThread extends Thread {
/**
* Default constructor.
*/
private ConnectionExpireThread() {
// do nothing
}
/**
* Provides a loop to handle cleaning up expired connections when the
* {@link ConnectionPool} becomes idle.
*/
public void run() {
// get the pool
ConnectionPool pool = ConnectionPool.this;
// determine the wait time
long waitTime = pool.getExpireTime() / 2L;
// loop until the pool is shutdown
while (!pool.isShutdown()) {
// wait for the wait time
synchronized (this) {
try {
this.wait(waitTime);
} catch (InterruptedException ignore) {
// do nothing
}
}
// synchronize
synchronized (pool) {
// check if shutdown
if (pool.isShutdown()) continue;
// check to see if pool is idle, if not let the pool handle it
if (pool.getIdleTime() < waitTime) continue;
// expire any old connections
try {
pool.expireConnections();
} catch (SQLException e) {
System.err.println(
"*** WARNING: Exception while expiring connections");
e.printStackTrace();
}
}
}
}
}
/**
* The {@link Connector} used for establishing new connections.
*/
private Connector connector = null;
/**
* The {@link TransactionIsolation} level to set on the {@link Connection}
* before returning it, or null
if none is to be set.
*/
private TransactionIsolation isolationLevel = null;
/**
* The {@link List} of all {@link PooledConnection} instances managed by
* the pool.
*/
private IdentityHashMap allConnections = null;
/**
* The {@link List} of {@link PooledConnection} instances that are available
* for lease from the pool.
*/
private List availableConnections = null;
/**
* The {@link IdentityHashMap} of currently leased {@link Connection}
* instances to the {@link PooledConnection} containing additional
* data associated with the connection.
*/
private IdentityHashMap leasedMap;
/**
* The minimum number of connections in the pool.
*/
private int minPoolSize = 0;
/**
* The maximum number of connections in the pool.
*/
private int maxPoolSize = 1;
/**
* The maximum number of milliseconds a {@link Connection} will be
* used before it is closed and replaced with a new {@link Connection}.
*/
private long expireTime = 0L;
/**
* The maximum number of times a {@link Connection} can be leased
* before it is replaced.
*/
private int retireLimit = 0;
/**
* The flag indicating if this pool has been shutdown.
*/
private boolean shutdown = false;
/**
* The greatest size the connection pool has grown to.
*/
private int greatestPoolSize = 0;
/**
* The total number of leased connections counted on each acquisition and
* release for measuring an average number of used connections from the pool.
*/
private long cumulativeLeaseCount = 0L;
/**
* The number of times that the leased count is checked.
*/
private long cumulativeLeaseChecks = 0L;
/**
* The greatest number of connections that have been acquired at any time.
*/
private int greatestLeasedCount = -1;
/**
* The greatest amount of time in milliseconds that a connection has been
* leased.
*/
private long greatestLeaseTime = -1L;
/**
* The total amount of time in milliseconds for all leases that have
* concluded.
*/
private long totalLeaseTime = 0L;
/**
* The number of {@link Connection} instances that have been expired due to
* the time they have been open exceeding a limit.
*/
private int expiredCount = 0;
/**
* The number of {@link Connection} instances that have been retired due
* to the number of times they have been leased exceeding a limit.
*/
private int retiredCount = 0;
/**
* The total number of connections that have been leased including those
* that are currently leased.
*/
private long totalLeaseCount = 0L;
/**
* The total number of connection leases that have completed.
*/
private long completedLeaseCount = 0;
/**
* The total number of milliseconds spent acquiring connections.
*/
private long totalAcquisitionTime = 0L;
/**
* The greatest number of milliseconds spent acquiring a connection.
*/
private long greatestAcquisitionTime = -1L;
/**
* The nanosecond timestamp when the last lease was obtained.
*/
private long idleStartTimeNanos = -1L;
/**
* The background expiration thread if one is needed.
*/
private ConnectionExpireThread expireThread = null;
/**
* The object to synchronize on when computing statistics.
*/
private final Object statsMonitor = new Object();
/**
* The constant for statistic units for connection units.
*/
private static final String CONNECTION_UNITS = "connections";
/**
* The constant for statistic units for millisecond units.
*/
private static final String MILLISECOND_UNITS = "ms";
/**
* The constant for statistic units that are measured in leases.
*/
private static final String LEASE_UNITS = "leases";
/**
* Enumerates the statistics associated with a {@link ConnectionPool}.
*/
public enum Stat implements Statistic {
/**
* The minimum number of {@link Connection} instances maintained
* in the {@link ConnectionPool}.
*/
minimumSize(CONNECTION_UNITS),
/**
* The maximum number of {@link Connection} instances to which the
* {@link ConnectionPool} may grow.
*/
maximumSize(CONNECTION_UNITS),
/**
* The current total number of {@link Connection} instances in the
* {@link ConnectionPool}.
*/
currentPoolSize(CONNECTION_UNITS),
/**
* The current number of available {@link Connection} instances in the
* {@link ConnectionPool}.
*/
availableConnections(CONNECTION_UNITS),
/**
* The current number of outstanding {@link Connection} leases that the
* {@link ConnectionPool} is waiting on.
*/
outstandingLeases(LEASE_UNITS),
/**
* The longest outstanding lease time in milliseconds. This statistic is
* absent if no currently outstanding leases.
*/
greatestOutstandingLeaseTime(MILLISECOND_UNITS),
/**
* The average outstanding lease time in milliseconds. This statistic is
* absernt if no currently outstanding leases.
*/
averageOutstandingLeaseTime(MILLISECOND_UNITS),
/**
* The maximum number of a milliseconds that a {@link Connection}
* will be used by the pool before it is closed and replaced. If
* no maximum exists then the value for this statistic is
* null
.
*/
expireTime(MILLISECOND_UNITS),
/**
* The maximum number of times that a {@link Connection} will be
* leased by the pool before it is closed and replaced. If no
* maximum exists then the value for this statistic is
* null
.
*/
retireLimit(LEASE_UNITS),
/**
* The greatest number of {@link Connection} instances that have
* been simultaneously leased. This is the greatest utilization
* of the {@link ConnectionPool}.
*/
greatestLeasedCount(CONNECTION_UNITS),
/**
* The average number of {@link Connection} instances that have been
* leased simultaneously. This is measured each time a {@link
* Connection} is acquired or released and the average number of those
* samples is returned.
*/
averageLeasedCount(CONNECTION_UNITS),
/**
* The greatest number of {@link Connection} instances to which the
* {@link ConnectionPool} has grown.
*/
greatestPoolSize(CONNECTION_UNITS),
/**
* The total number of {@link Connection} instances that have been expired
* due to exceeding the maximum allowed lifespan.
*/
expiredConnections(CONNECTION_UNITS),
/**
* The total number of {@link Connection} instances that have been retired
* due to be leased the maximum number of times.
*/
retiredConnections(CONNECTION_UNITS),
/**
* The average number of milliseconds taken to acquire a {@link
* Connection} from the {@link ConnectionPool}.
*/
averageAcquireTime(MILLISECOND_UNITS),
/**
* The greatest number of milliseconds taken to acquire a {@link
* Connection} from the {@link ConnectionPool}.
*/
greatestAcquireTime(MILLISECOND_UNITS),
/**
* The greatest number of milliseconds that a {@link Connection}
* has been leased from the {@link ConnectionPool}. This includes
* currently leased connections.
*/
greatestLeaseTime(MILLISECOND_UNITS),
/**
* The average number of milliseconds for all {@link Connection}
* leases including those currently leased.
*/
averageLeaseTime(MILLISECOND_UNITS),
/**
* The total number of {@link Connection} leases that have been
* granted by this pool.
*/
lifetimeLeaseCount(LEASE_UNITS),
/**
* The number of milliseconds that have passed since the last time a
* {@link Connection} lease was requested. If a {@link Connection} lease
* has never been requested then this returns the number of milliseconds
* since the construction of the {@link ConnectionPool}.
*/
idleTime(MILLISECOND_UNITS);
/**
* Constructs with the specified units.
*
* @param units The units for the statistics.
*/
Stat(String units) {
this.units = units;
}
/**
* Gets the description of the units for the statistic.
*
* @return The description of the units for the statistic.
*/
@Override
public String getUnits() {
return this.units;
}
/**
* The units for the statistic.
*/
private String units = null;
}
/**
* Constructs with the {@link Connector} and the size of the connection pool.
*
* @param connector The {@link Connector} for establishing new database
* connections.
* @param poolSize The number of connections to initialize the connection
* pool with.
* @throws SQLException If a failure occurs in establishing a connection.
* @throws IllegalArgumentException If the specified pool size is less-than
* or equal-to zero (0).
* @throws NullPointerException If the specified {@link Connector} is
* null
.
*/
public ConnectionPool(Connector connector, int poolSize)
throws SQLException, IllegalArgumentException, NullPointerException {
this(connector, null, poolSize);
}
/**
* Constructs with the {@link Connector} and the size of the connection pool.
*
* @param connector The {@link Connector} for establishing new database
* connections.
* @param isolationLevel The {@link TransactionIsolation} level to ensure is
* set on each {@link Connection} before providing it,
* or null
if the isolation level need not
* be verified and set.
* @param poolSize The number of connections to initialize the connection
* pool with.
* @throws SQLException If a failure occurs in establishing a connection.
* @throws IllegalArgumentException If the specified pool size is less-than
* or equal-to zero (0).
* @throws NullPointerException If the specified {@link Connector} is
* null
.
*/
public ConnectionPool(Connector connector,
TransactionIsolation isolationLevel,
int poolSize)
throws SQLException, IllegalArgumentException, NullPointerException {
this(connector, isolationLevel, poolSize, poolSize);
}
/**
* Constructs with the specified {@link Connector} for establishing new
* JDBC connections, a minimum connection pool size and maximum connection
* pool size. The pool will start at the minimum size and grow to the
* maximum size if demand for connections requires it.
*
* @param connector The {@link Connector} for establishing new database
* connections.
* @param minPoolSize The minimum number of connections to initialize the
* connection pool with.
* @param maxPoolSize The maximum number of connections that the pool can
* grow to have.
* @throws SQLException If a failure occurs in establishing a connection.
* @throws IllegalArgumentException If the specified maximum pool size is
* less-than or equal-to zero (0), the
* minimum pool size is less-than zero (0),
* or the minimum pool size is greater-than
* the maximum pool size.
* @throws NullPointerException If the specified {@link Connector} is
* null
.
*/
public ConnectionPool(Connector connector,
int minPoolSize,
int maxPoolSize)
throws SQLException, IllegalArgumentException, NullPointerException {
this(connector, null, minPoolSize, maxPoolSize);
}
/**
* Constructs with the specified {@link Connector} for establishing new
* JDBC connections, a minimum connection pool size and maximum connection
* pool size. The pool will start at the minimum size and grow to the
* maximum size if demand for connections requires it.
*
* @param connector The {@link Connector} for establishing new database
* connections.
* @param isolationLevel The {@link TransactionIsolation} level to ensure is
* set on each {@link Connection} before providing it,
* or null
if the isolation level need not
* be verified and set.
* @param minPoolSize The minimum number of connections to initialize the
* connection pool with.
* @param maxPoolSize The maximum number of connections that the pool can
* grow to have.
* @throws SQLException If a failure occurs in establishing a connection.
* @throws IllegalArgumentException If the specified maximum pool size is
* less-than or equal-to zero (0), the
* minimum pool size is less-than zero (0),
* or the minimum pool size is greater-than
* the maximum pool size.
* @throws NullPointerException If the specified {@link Connector} is
* null
.
*/
public ConnectionPool(Connector connector,
TransactionIsolation isolationLevel,
int minPoolSize,
int maxPoolSize)
throws SQLException, IllegalArgumentException, NullPointerException {
this(connector, isolationLevel, minPoolSize, maxPoolSize, 0, 0);
}
/**
* Constructs with the specified {@link Connector} for establishing new
* JDBC connections, a minimum connection pool size and maximum connection
* pool size. The pool will start at the minimum size and grow to the
* maximum size if demand for connections requires it. Additionally, optional
* limits on the lifespan of a {@link Connection} (an expire time) and on the
* number of times a {@link Connection} will be leased (a retire count) can
* be specified as non-zero values to impose limits.
*
* @param connector The {@link Connector} for establishing new database
* connections.
* @param minPoolSize The minimum number of connections to initialize the
* connection pool with.
* @param maxPoolSize The maximum number of connections that the pool can
* grow to have.
* @param expireTime The maximum number of seconds that a
* {@link Connection} will be used before it is closed and
* replaced, or zero (0) then {@link Connection} instances
* will not be expired based on time.
* @param retireLimit The maximum number of times a {@link Connection}
* will be leased before it is closed and replaced, or
* zero (0) if {@link Connection} instances should not be
* retired after exceeding a number of leases.
* @throws SQLException If a failure occurs in establishing a connection.
* @throws IllegalArgumentException If the specified maximum pool size is
* less-than or equal-to zero (0), the
* minimum pool size is less-than zero (0),
* or the minimum pool size is greater-than
* the maximum pool size or if the maximum
* lifespan or number of leases are less-than
* or equal-to zero (0).
* @throws NullPointerException If the specified {@link Connector} is
* null
.
*/
public ConnectionPool(Connector connector,
int minPoolSize,
int maxPoolSize,
int expireTime,
int retireLimit)
throws SQLException, IllegalArgumentException, NullPointerException
{
this(connector, null, minPoolSize, maxPoolSize, expireTime, retireLimit);
}
/**
* Constructs with the specified {@link Connector} for establishing new
* JDBC connections, a minimum connection pool size and maximum connection
* pool size. The pool will start at the minimum size and grow to the
* maximum size if demand for connections requires it. Additionally, optional
* limits on the lifespan of a {@link Connection} (an expire time) and on the
* number of times a {@link Connection} will be leased (a retire count) can
* be specified as non-zero values to impose limits.
*
* @param connector The {@link Connector} for establishing new database
* connections.
* @param isolationLevel The {@link TransactionIsolation} level to ensure is
* set on each {@link Connection} before providing it,
* or null
if the isolation level need not
* be verified and set.
* @param minPoolSize The minimum number of connections to initialize the
* connection pool with.
* @param maxPoolSize The maximum number of connections that the pool can
* grow to have.
* @param expireTime The maximum number of seconds that a
* {@link Connection} will be used before it is closed and
* replaced, or zero (0) then {@link Connection} instances
* will not be expired based on time.
* @param retireLimit The maximum number of times a {@link Connection}
* will be leased before it is closed and replaced, or
* zero (0) if {@link Connection} instances should not be
* retired after exceeding a number of leases.
* @throws SQLException If a failure occurs in establishing a connection.
* @throws IllegalArgumentException If the specified maximum pool size is
* less-than or equal-to zero (0), the
* minimum pool size is less-than zero (0),
* or the minimum pool size is greater-than
* the maximum pool size or if the maximum
* lifespan or number of leases are less-than
* or equal-to zero (0).
* @throws NullPointerException If the specified {@link Connector} is
* null
.
*/
public ConnectionPool(Connector connector,
TransactionIsolation isolationLevel,
int minPoolSize,
int maxPoolSize,
int expireTime,
int retireLimit)
throws SQLException, IllegalArgumentException, NullPointerException
{
// check if the connector is null
Objects.requireNonNull(connector,
"The specified connector cannot be null");
// check the min pool size is non-negative
if (minPoolSize < 0) {
throw new IllegalArgumentException(
"The minimum pool size cannot be negative: " + minPoolSize);
}
if (maxPoolSize <= 0) {
throw new IllegalArgumentException(
"The maximum pool size must be a positive number: " + maxPoolSize);
}
// check the maximum and minimum pool sizes
if (minPoolSize > maxPoolSize) {
throw new IllegalArgumentException(
"Minimum pool size (" + minPoolSize
+ ") cannot exceeed maximum poll size (" + maxPoolSize + ").");
}
// check the max connection lifespan
if (expireTime < 0) {
throw new IllegalArgumentException(
"The maximum connection lifespan (expire time) cannot be negative: "
+ expireTime);
}
// check the max connection leases
if (expireTime < 0) {
throw new IllegalArgumentException(
"The maximum connection leases (retire count) cannot be negative: "
+ retireLimit);
}
// set the fields
this.connector = connector;
this.isolationLevel = isolationLevel;
this.minPoolSize = minPoolSize;
this.maxPoolSize = maxPoolSize;
this.expireTime = (expireTime * 1000);
this.retireLimit = retireLimit;
// loop through and populate the initial connections
this.allConnections = new IdentityHashMap<>();
for (int index = 0; index < this.minPoolSize; index++) {
PooledConnection pooledConnection = new PooledConnection(
this.connector.openConnection());
this.allConnections.put(pooledConnection,
pooledConnection.getCreatedTimeNanos());
}
// initialize the greatest pool size
this.greatestPoolSize = this.allConnections.size();
// create the list of available connections
this.availableConnections = new LinkedList<>();
for (PooledConnection conn : this.allConnections.keySet()) {
this.availableConnections.add(conn);
}
// create the identity hash map for leases
this.leasedMap = new IdentityHashMap<>();
// check the expire time
if (this.expireTime > 0L) {
this.expireThread = new ConnectionExpireThread();
this.expireThread.start();
} else {
this.expireThread = null;
}
// initialize the idle start time
this.idleStartTimeNanos = System.nanoTime();
}
/**
* Gets the {@link TransactionIsolation} level that is ensured on the
* {@link Connection} instances when they are acquired. This returns
* null
if no isolation level is being enforced.
*
* @return The {@link TransactionIsolation} level that is ensured on the
* {@link Connection} instances when they are acquired, or
* null
if none is enforced.
*/
public TransactionIsolation getIsolationLevel() {
return this.isolationLevel;
}
/**
* Obtains a diagnostic string describing where each leased connection was obtained
* and the state of the current lease holder threads.
*
* @return The diagnostic {@link String} describing the state of this instance.
*/
public synchronized String getDiagnosticLeaseInfo() {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
leasedMap.values().forEach((pooledConnection) -> {
PooledConnectionHandler handler = pooledConnection.getCurrentLeaseHandler();
pw.println();
pw.println(handler.getDiagnosticInfo());
});
return sw.toString();
}
/**
* Gets the statistics for this instance as a {@link Map} of {@link Stat}
* keys to {@link Number} values.
*
* @return The statistics for this instance as a {@link Map} of {@link
* Stat} keys to {@link Number} values.
*/
public Map getStatistics() {
Map result = new LinkedHashMap<>();
synchronized (this) {
putStat(result, minimumSize, this.getMinimumSize());
putStat(result, maximumSize, getMaximumSize());
putStat(result, Stat.expireTime, this.getExpireTime());
putStat(result, Stat.retireLimit, this.getRetireLimit());
putStat(result, Stat.greatestLeasedCount,
this.getGreatestLeasedCount());
putStat(result, averageLeasedCount, this.getAverageLeasedCount());
putStat(result, Stat.greatestPoolSize, this.getGreatestPoolSize());
putStat(result, expiredConnections, this.getExpiredConnectionCount());
putStat(result, retiredConnections, this.getRetiredConnectionCount());
putStat(result, averageAcquireTime, this.getAverageAcquisitionTime());
putStat(result, greatestAcquireTime, this.getGreatestAcquisitionTime());
putStat(result, Stat.greatestLeaseTime, this.getGreatestLeaseTime());
putStat(result, averageLeaseTime, this.getAverageLeaseTime());
putStat(result, lifetimeLeaseCount, this.getLifetimeLeaseCount());
putStat(result, currentPoolSize, this.getCurrentPoolSize());
putStat(result, Stat.availableConnections,
this.getAvailableConnectionCount());
putStat(result, outstandingLeases, this.getOutstandingLeaseCount());
putStat(result, greatestOutstandingLeaseTime,
this.getGreatestOutstandingLeaseTime());
putStat(result, averageOutstandingLeaseTime,
this.getAverageOutstandingLeaseTime());
putStat(result, idleTime, this.getIdleTime());
}
return result;
}
/**
* Puts a {@link Stat} key and {@link Number} value in the specified
* {@link Map} if the specified value is not null
. It does
* nothing if the value is null
.
*
* @param map The {@link Map} to populate.
* @param key The {@link Statistic} key for the map.
* @param value The value to associate with the key.
*/
private static void putStat(Map map,
Stat key,
Number value)
{
if (value == null) return;
map.put(key, value);
}
/**
* Computes the number of milliseconds since the specified starting
* nanosecond system time.
*
* @param startNanos The starting nanosecond time.
* @return The elapsed number of milliseconds.
*/
private static long elapsed(long startNanos) {
return (System.nanoTime() - startNanos) / ONE_MILLION;
}
/**
* Acquires a {@link Connection} from the {@link ConnectionPool}, blocking
* indefinitely if necessary. If no {@link Connection} instances are
* available and the pool has not reached its {@linkplain #getMaximumSize()
* maximum size} then a new {@link Connection} is opened.
*
* @return The {@link Connection} acquired from the pool.
*
* @throws SQLException If a failure occurs.
*/
public Connection acquire() throws SQLException {
return this.acquire(-1L);
}
/**
* Acquires a {@link Connection} from the {@link ConnectionPool}, blocking
* for the specified maximum wait time if necessary. If no {@link Connection}
* instances are available and the pool has not reached its {@linkplain
* #getMaximumSize() maximum size} then a new {@link Connection} is opened.
* If the specified wait time is zero (0) then this method will not wait for
* a {@link Connection} to become available, but will immediately return a
* {@link Connection} if one is available or if the {@link ConnectionPool} can
* grow/expand to make one available, otherwise it returns null
.
* Finally, a negative wait time can be specified to indicate no maximum
* wait time and therefore indefinite waiting for a {@link Connection} to
* become available.
*
* @param maxWait The maximum about af time (in milliseconds) to wait for a
* {@link Connection} to become available, or zero (0) if not
* waiting at all (only returning a {@link Connection} if one
* is immediately available, or a negative number if no
* maximum wait time and willing to wait indefinitely.
*
* @return The {@link Connection} acquired from the pool, or null
* if a connection could not be obtained within the time allotted.
*
* @throws SQLException If a failure occurs.
*/
public Connection acquire(long maxWait)
throws SQLException, IllegalArgumentException
{
final long startTime = System.nanoTime();
PooledConnection acquired = null;
Integer newPoolSize = null;
Integer leasedCount = null;
synchronized (this) {
// loop until we have an acquired connection
while (acquired == null && !this.isShutdown()
&& (maxWait <= 0L || elapsed(startTime) < maxWait))
{
// wait for a connection to become available
while (!this.isShutdown() && (this.availableConnections.size() == 0)
&& (this.allConnections.size() == this.getMaximumSize())
&& (maxWait < 0L || elapsed(startTime) < maxWait))
{
// unless no-wait was specified, then wait for a connection
if (maxWait != 0L) {
try {
long timeout = (maxWait < 0L) ? WAIT_TIMEOUT
: Math.min(WAIT_TIMEOUT, maxWait - elapsed(startTime));
if (timeout < 0L) break;
this.wait(timeout);
} catch (InterruptedException ignore) {
// do nothing
}
}
}
// now check if shutdown
if (this.isShutdown()) {
throw new SQLException(
"Unable to obtain a connection because the connection pool was "
+ "shutdown");
}
// check if we have any connections that have exceeded their maximum
// lifespan (we handle maximum leases on release)
Long maxLifespan = this.getExpireTime();
if (maxLifespan != null) {
this.expireConnections();
// record the pool size
newPoolSize = this.allConnections.size();
}
// check if we should grow the pool
if (this.availableConnections.size() == 0
&& this.allConnections.size() < this.getMaximumSize())
{
// create a new pooled connection
acquired = new PooledConnection(this.connector.openConnection());
this.allConnections.put(acquired, acquired.getCreatedTimeNanos());
// record the new pool size
newPoolSize = this.allConnections.size();
} else if (this.availableConnections.size() > 0) {
// acquire the first connection
acquired = this.availableConnections.remove(0);
}
// check if no wait and skip looping
if (maxWait == 0L) break;
}
// check if shutdown
if (acquired == null && this.isShutdown()) {
throw new SQLException(
"Unable to obtain a connection because the connection pool was "
+ "shutdown");
}
// we must have an acquired connection
if (acquired == null && maxWait < 0L) {
// we must have an acquired connection
throw new IllegalStateException(
"Exited wait loop, but did not acquire a pooled connection.");
}
// check if we acquired a connection
if (acquired != null) {
// we must have an acquired connection
// create a handler
PooledConnectionHandler handler
= new PooledConnectionHandler(this, acquired.getConnection());
// set the handler
acquired.setCurrentLeaseHandler(handler);
// record the lease
this.leasedMap.put(handler.getProxiedConnection(), acquired);
}
// set the acquired count
leasedCount = this.leasedMap.size();
// notify all
this.notifyAll();
// compute the statistics
if (acquired != null) {
// compute acquisition time stats
long acquisitionTime = (System.nanoTime() - startTime) / ONE_MILLION;
this.totalAcquisitionTime += acquisitionTime;
if (acquisitionTime > this.greatestAcquisitionTime) {
this.greatestAcquisitionTime = acquisitionTime;
}
}
// check if the greatest pool size increased
if (newPoolSize != null && newPoolSize > this.greatestPoolSize) {
this.greatestPoolSize = newPoolSize;
}
// compute based on the number of concurrent leases
if (leasedCount != null) {
this.cumulativeLeaseCount += leasedCount;
this.cumulativeLeaseChecks++;
if (leasedCount > this.greatestLeasedCount) {
this.greatestLeasedCount = leasedCount;
}
}
// increment the lease count if we acquired a connection
if (acquired != null) {
this.totalLeaseCount++;
}
// set the timestamp for the last lease
if (acquired != null) {
this.idleStartTimeNanos = System.nanoTime();
}
}
// if no connection acquired, return null
if (acquired == null) {
return null;
}
// get the connection
Connection conn = acquired.getConnection();
// ensure auto-commit is turned off
if (conn.getAutoCommit()) conn.setAutoCommit(false);
// check isolation level
if (this.getIsolationLevel() != null) {
this.getIsolationLevel().applyTo(conn);
}
// return the proxied connection
return acquired.getCurrentLeaseHandler().getProxiedConnection();
}
/**
* Releases the specified {@link Connection} back to the {@link
* ConnectionPool}. If the specified {@link Connection} did not come from
* this {@link ConnectionPool} instance then an {@link
* IllegalArgumentException} is thrown. If the specified {@link Connection}
* is from this pool, but has already been released, then this method does
* nothing.
*
* @param connection The {@link Connection} to release, or null
* if none was acquired and therefore none need be released.
*
* @throws SQLException If a JDBC failure occurs.
*/
public void release(Connection connection) throws SQLException {
// check if null -- allow for easy semantics in finally blocks
// when acquisition times out and null is returned
if (connection == null) return;
// init the stats tracking variables
Long leasedTime = null;
Integer leasedCount = null;
int retired = 0;
synchronized (this) {
// make sure we notify after relinquishing lock
this.notifyAll();
// check if this is one of our connections
InvocationHandler handler = null;
try {
handler = Proxy.getInvocationHandler(connection);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
"The specified Connection is not from this pool instance ("
+ "not a proxy).");
}
if (!(handler instanceof PooledConnectionHandler)) {
throw new IllegalArgumentException(
"The specified Connection is not from this pool instance ("
+ "wrong handler type): " + handler.getClass().getName());
}
PooledConnectionHandler pch = (PooledConnectionHandler) handler;
if (pch.getPool() != this) {
throw new IllegalArgumentException(
"The specified Connection is not from this pool instance ("
+ "wrong pool instance).");
}
// get the associated pooled connection
PooledConnection pooledConn = this.leasedMap.get(connection);
// check if null (i.e.: already released)
if (pooledConn == null) {
Exception exception
= new Exception("WARNING: Connection released more than once");
System.err.println();
System.err.println("-------------------------------------------------");
exception.printStackTrace();
return;
}
// if not null then do some cleanup and release
this.leasedMap.remove(connection);
// rollback anything not committed in the transaction
Connection backingConn = pooledConn.getConnection();
if (!backingConn.getAutoCommit()) {
backingConn.rollback();
}
backingConn.setAutoCommit(false);
// get the new leased count
leasedCount = this.leasedMap.size();
// ensure the handler is marked as closed
pch.markClosed();
// check the lease time
leasedTime = pch.getLeaseTime();
// clear the handler on the pooled connection
pooledConn.setCurrentLeaseHandler(null);
// conditionally make the connection available again
if (this.getRetireLimit() != null
&& pooledConn.getLeaseCount() > this.getRetireLimit())
{
// remove from the list of all connections
this.allConnections.remove(pooledConn);
// close the connection
SQLUtilities.close(pooledConn.getConnection());
// increment the retired count
retired++;
// check if we have dropped below the minimum
if (this.allConnections.size() < this.getMinimumSize()) {
try {
PooledConnection refill
= new PooledConnection(this.connector.openConnection());
this.allConnections.put(refill, refill.getCreatedTimeNanos());
this.availableConnections.add(refill);
} catch (SQLException ignore) {
ignore.printStackTrace();
}
}
} else {
this.availableConnections.add(pooledConn);
}
// update the statistics
if (leasedTime != null) {
if (leasedTime > this.greatestLeaseTime) {
this.greatestLeaseTime = leasedTime;
}
this.totalLeaseTime += leasedTime;
}
if (leasedCount != null) {
this.cumulativeLeaseCount += leasedCount;
this.cumulativeLeaseChecks++;
if (leasedCount > this.greatestLeasedCount) {
this.greatestLeasedCount = leasedCount;
}
}
this.retiredCount += retired;
this.completedLeaseCount++;
}
}
/**
* Checks if this {@link ConnectionPool} has been shutdown.
*
* @return true
if this {@link ConnectionPool} has been
* shutdown, otherwise false
.
*/
public synchronized boolean isShutdown() {
return this.shutdown;
}
/**
* Sets the shutdown flag and notifies all that the shutdown process has
* begun.
*
*/
public void shutdown() {
// set the shutdown flag and notify all
synchronized (this) {
if (this.shutdown) return;
this.shutdown = true;
this.notifyAll();
}
if (this.expireThread != null) {
synchronized (this.expireThread) {
this.expireThread.notifyAll();
}
}
// now wait for all connections to become available
synchronized (this) {
while (this.availableConnections.size() < this.allConnections.size()) {
try {
this.wait(WAIT_TIMEOUT);
} catch (InterruptedException ignore) {
// do nothing
}
}
// once we get here, it is time to close all connections
for (PooledConnection pooledConn : this.allConnections.keySet()) {
SQLUtilities.close(pooledConn.getConnection());
}
// clear the lists
this.availableConnections.clear();
this.allConnections.clear();
this.leasedMap.clear();
}
// join with the expire thread
if (this.expireThread != null) {
try {
this.expireThread.join();
} catch (InterruptedException ignore) {
// do nothing
}
}
}
/**
* Gets the minimum number of {@link Connection} instances to be maintained
* by this {@link ConnectionPool}.
*
* @return The minimum number of {@link Connection} instances to be
* maintained by this {@link ConnectionPool}.
*/
public int getMinimumSize() {
return this.minPoolSize;
}
/**
* Gets the maximum number of {@link Connection} instances to which this
* {@link ConnectionPool} can grow.
*
* @return The maximum number of {@link Connection} instances to which this
* {@link ConnectionPool} can grow.
*/
public int getMaximumSize() {
return this.maxPoolSize;
}
/**
* Gets the maximum number of milliseconds a {@link Connection} in this
* pool will be used before it is closed and replaced. This returns
* null
if no maximum lifespan is configured for this pool.
*
* @return The maximum number of milliseconds a {@link Connection} in this
* pool will be used before it is closed and replaced, or
* null
if no maximum is configured.
*/
public Long getExpireTime() {
if (this.expireTime <= 0) {
return null;
} else {
return this.expireTime;
}
}
/**
* Gets the maximum number of times a {@link Connection} will be leased from
* this pool before it is closed and replaced. This returns null
* if no maximum number of leases is configured for this pool.
*
* @return The maximum number of times a {@link Connection} will be leased
* from this pool before it is closed and replaced, or
* null
if no maximum number of leases is configured.
*/
public Integer getRetireLimit() {
if (this.retireLimit <= 0) {
return null;
} else {
return this.retireLimit;
}
}
/**
* Gets the greatest number of {@link Connection} instances that have been
* concurrently leased from the pool at any given time. This returns
* null
if no connections have been leased.
*
* @return The greatest number of {@link Connection} instances that have been
* concurrently leased from the pool at any given time, or
* null
if none have ever been leased.
*/
public Integer getGreatestLeasedCount() {
synchronized (this) {
if (this.greatestLeasedCount < 0) {
return null;
} else {
return this.greatestLeasedCount;
}
}
}
/**
* Returns the average number of {@link Connection} instances that have been
* acquired from the pool at each time when a {@link Connection} is acquired
* or released. That is the count is taken upon each call to {@link
* #acquire()} and {@link #release(Connection)} that acquires or releases a
* {@link Connection} and the total of the samples is divided by the number
* of times a sample was taken. If no connections have been acquired then
* this returns null
.
*
* @return The average number of {@link Connection} instances that have been
* acquired from the pool at each time when a {@link Connection} is
* acquired or released, or null
if none have been
* acquired.
*/
public Double getAverageLeasedCount() {
synchronized (this) {
if (this.totalLeaseCount <= 0 || this.cumulativeLeaseChecks <= 0) {
return null;
} else {
double acquireCount = (double) this.cumulativeLeaseCount;
double checkCount = (double) this.cumulativeLeaseChecks;
return acquireCount / checkCount;
}
}
}
/**
* Returns the greatest number of connections that the pool has grown to
* over its lifespan. If no connections have ever been created then this
* returns zero (0).
*
* @return The greatest number of connections that the pool has grown to
* over its lifespan.
*/
public Integer getGreatestPoolSize() {
synchronized (this) {
return this.greatestPoolSize;
}
}
/**
* Gets the number of {@link Connection} instances that have been expired
* due to exceeding the {@linkplain #getExpireTime() maximum lifespan limit}.
* This returns null
if no such maximum limits is set.
*
* @return The number of {@link Connection} instances that have been expired,
* or null
if connections are not being expired.
*/
public Integer getExpiredConnectionCount() {
synchronized (this) {
if (this.getExpireTime() == null) {
return null;
} else {
return this.expiredCount;
}
}
}
/**
* Gets the number of {@link Connection} instances that have been retired
* due to reaching the {@linkplain #getRetireLimit() maximum lease limit}.
* This returns null
if no such maximum limit is set.
*
* @return The number of {@link Connection} instances that have been retired,
* or null
if connections are not being retired.
*/
public Integer getRetiredConnectionCount() {
synchronized (this) {
if (this.getRetireLimit() == null) {
return null;
} else {
return this.retiredCount;
}
}
}
/**
* Gets the average amount of time in milliseconds that it takes to
* acquire a connection from the pool. This does not include attempts to
* acquire a {@link Connection} that fail within the allotted time limit.
* This returns null
if no connections have been acquired.
*
* @return The average amount of time in milliseconds that it takes to
* acquire a connection from the pool, or null
if no
* connections have been acquired.
*/
public Double getAverageAcquisitionTime() {
synchronized (this) {
if (this.totalAcquisitionTime < 0 || this.totalLeaseCount <= 0) {
return null;
} else {
double totalTime = (double) this.totalAcquisitionTime;
double totalCount = (double) this.totalLeaseCount;
return (totalTime / totalCount);
}
}
}
/**
* Gets the greatest amount of time in milliseconds that it has taken to
* acquire a connection from the pool. This returns null
if
* no connections have been acquired.
*
* @return The greatest amount of time in milliseconds that it has taken to
* acquire a connection from the pool, or null
if no
* connections have been acquired.
*/
public Long getGreatestAcquisitionTime() {
synchronized (this) {
if (this.totalLeaseCount == 0 || this.greatestAcquisitionTime < 0L) {
return null;
} else {
return this.greatestAcquisitionTime;
}
}
}
/**
* Gets the greatest amount of time that a connection has been leased.
* This includes connections that are currently leased and have not yet
* been released back to the pool. If no connections have been ever
* been leased from this pool instance, then null
is returned.
*
* @return The greatest amount of time that a connection has been leased,
* or null
if no connections have been leased from this
* pool.
*/
public Long getGreatestLeaseTime() {
long greatestTime = -1L;
synchronized (this) {
for (PooledConnection pooledConn: this.leasedMap.values()) {
PooledConnectionHandler handler = pooledConn.getCurrentLeaseHandler();
Long leaseTime = handler.getLeaseTime();
if (leaseTime != null && leaseTime > greatestTime) {
greatestTime = leaseTime;
}
}
if (this.totalLeaseCount == 0) {
return null;
} else {
return Math.max(greatestTime, this.greatestLeaseTime);
}
}
}
/**
* Gets the average amount of time connections are leased. This only
* includes times for leases that have completed (i.e.: connections that have
* been released back to the pool). If no connections have completed their
* leases for this pool instance, then null
is returned.
*
* @return The average amount of time it has taken to complete a connection
* lease, or null
if no connection leases have completed.
*/
public Double getAverageLeaseTime() {
synchronized (this) {
if (this.completedLeaseCount == 0) {
return null;
} else {
double totalTime = (double) this.totalLeaseTime;
double totalCount = (double) this.totalLeaseCount;
return (totalTime / totalCount);
}
}
}
/**
* Gets the current total number of {@link Connection} instances in this
* {@link ConnectionPool} instance.
*
* @return The current total number of {@link Connection} instances in this
* {@link ConnectionPool} instance.
*/
public int getCurrentPoolSize() {
synchronized (this) {
return this.allConnections.size();
}
}
/**
* Gets the current number of available {@link Connection} instances in this
* {@link ConnectionPool} instance.
*
* @return The current number of available {@link Connection} instances in this
* {@link ConnectionPool} instance.
*/
public int getAvailableConnectionCount() {
synchronized (this) {
return this.availableConnections.size();
}
}
/**
* Gets the current number of outstanding {@link Connection} leases on which
* this {@link ConnectionPool} is waiting.
*
* @return The current number of outstanding {@link Connection} leases on
* which this {@link ConnectionPool} is waiting.
*/
public int getOutstandingLeaseCount() {
synchronized (this) {
return this.leasedMap.size();
}
}
/**
* Gets the longest outstanding lease time in milliseconds. This returns
* null
if there are currently no outstanding leases.
*
* @return The longest outstanding lease time in milliseconds, or
* null
if there are currently no outstanding leases.
*/
public Long getGreatestOutstandingLeaseTime() {
synchronized (this) {
if (this.leasedMap.size() == 0) return null;
long greatestTime = -1L;
for (PooledConnection pooledConn : this.leasedMap.values()) {
long leaseTime = pooledConn.getCurrentLeaseHandler().getLeaseTime();
if (leaseTime > greatestTime) {
greatestTime = leaseTime;
}
}
return greatestTime;
}
}
/**
* Gets the average outstanding lease time in milliseconds. This returns
* null
if there are currently no outstanding leases.
*
* @return The average outstanding lease time in milliseconds, or
* null
if there are currently no outstanding leases.
*/
public Double getAverageOutstandingLeaseTime() {
synchronized (this) {
if (this.leasedMap.size() == 0) return null;
long totalTime = 0L;
for (PooledConnection pooledConn : this.leasedMap.values()) {
totalTime += pooledConn.getCurrentLeaseHandler().getLeaseTime();
}
double leaseCount = (double) this.leasedMap.size();
return ((double) totalTime) / leaseCount;
}
}
/**
* Gets the number of milliseconds that have past since the last {@link
* Connection} lease was requested. This returns the number of milliseconds
* since construction if a {@link Connection} lease has never been requested.
*
* @return The number of milliseconds that have past since the last {@link
* Connection} lease was requested, or the number of milliseconds
* since construction if a {@link Connection} lease has never been
* requested.
*/
public long getIdleTime() {
synchronized (this) {
return (System.nanoTime() - this.idleStartTimeNanos) / ONE_MILLION;
}
}
/**
* Gets the total number of leases that have been granted over the lifetime
* of this {@link ConnectionPool}.
*
* @return The total number of leases that have been granted over the lifetime
* of this {@link ConnectionPool}.
*/
public long getLifetimeLeaseCount() {
synchronized (this) {
return this.totalLeaseCount;
}
}
/**
* Handles expiring connections that have exceeded their maximum lifespan
* and refilling the pool to the minimum size as needed.
*
* @throws SQLException If a failure occurs.
*/
protected synchronized void expireConnections() throws SQLException {
// check if no expiration defined
if (this.getExpireTime() == null) return;
int expired = 0;
long maxLifespan = this.getExpireTime();
// get an iterator
Iterator iter = this.availableConnections.iterator();
while (iter.hasNext()) {
// get the next pooled connection
PooledConnection pooledConn = iter.next();
// check if it has exceeded the maximum lifespan
if (pooledConn.getLifespan() > maxLifespan) {
// close the connection
SQLUtilities.close(pooledConn.getConnection());
// increment the expired count
expired++;
// remove from the available connections
iter.remove();
// remove from all connections
this.allConnections.remove(pooledConn);
}
}
// now check if we need to refill to maintain the minimum pool size
while (this.allConnections.size() < this.getMinimumSize()) {
PooledConnection refill
= new PooledConnection(this.connector.openConnection());
this.allConnections.put(refill, refill.getCreatedTimeNanos());
this.availableConnections.add(refill);
}
// update the number of expired connections
this.expiredCount += expired;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy