
io.ebean.datasource.pool.ConnectionPool Maven / Gradle / Ivy
package io.ebean.datasource.pool;
import io.ebean.datasource.DataSourceAlert;
import io.ebean.datasource.DataSourceConfig;
import io.ebean.datasource.DataSourceConfigurationException;
import io.ebean.datasource.DataSourceInitialiseException;
import io.ebean.datasource.DataSourcePool;
import io.ebean.datasource.DataSourcePoolListener;
import io.ebean.datasource.InitDatabase;
import io.ebean.datasource.PoolStatistics;
import io.ebean.datasource.PoolStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Statement;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* A robust DataSource implementation.
*
*
* - Manages the number of connections closing connections that have been idle for some time.
* - Notifies when the datasource goes down and comes back up.
* - Provides PreparedStatement caching
* - Knows the busy connections
* - Traces connections that have been leaked
*
*
*/
public class ConnectionPool implements DataSourcePool {
private static final Logger logger = LoggerFactory.getLogger(ConnectionPool.class);
/**
* The name given to this dataSource.
*/
private final String name;
private final DataSourceConfig config;
/**
* Used to notify of changes to the DataSource status.
*/
private final DataSourceAlert notify;
/**
* Optional listener that can be notified when connections are got from and
* put back into the pool.
*/
private final DataSourcePoolListener poolListener;
/**
* Properties used to create a Connection.
*/
private final Properties connectionProps;
/**
* Queries, that are run for each connection on first open.
*/
private final List initSql;
/**
* The jdbc connection url.
*/
private final String databaseUrl;
/**
* The jdbc driver.
*/
private final String databaseDriver;
/**
* The sql used to test a connection.
*/
private final String heartbeatsql;
private final int heartbeatFreqSecs;
private final int heartbeatTimeoutSeconds;
private final long trimPoolFreqMillis;
/**
* The transaction isolation level as per java.sql.Connection.
*/
private final int transactionIsolation;
/**
* The default autoCommit setting for Connections in this pool.
*/
private final boolean autoCommit;
private final boolean readOnly;
private final boolean failOnStart;
/**
* Max idle time in millis.
*/
private final int maxInactiveMillis;
/**
* Max age a connection is allowed in millis.
* A value of 0 means no limit (no trimming based on max age).
*/
private final long maxAgeMillis;
/**
* Flag set to true to capture stackTraces (can be expensive).
*/
private boolean captureStackTrace;
/**
* The max size of the stack trace to report.
*/
private final int maxStackTraceSize;
/**
* flag to indicate we have sent an alert message.
*/
private boolean dataSourceDownAlertSent;
/**
* The time the pool was last trimmed.
*/
private long lastTrimTime;
/**
* Assume that the DataSource is up. heartBeat checking will discover when
* it goes down, and comes back up again.
*/
private boolean dataSourceUp = true;
/**
* Stores the dataSourceDown-reason (if there is any)
*/
private SQLException dataSourceDownReason;
/**
* The current alert.
*/
private AtomicBoolean inWarningMode = new AtomicBoolean();
/**
* The minimum number of connections this pool will maintain.
*/
private int minConnections;
/**
* The maximum number of connections this pool will grow to.
*/
private int maxConnections;
/**
* The number of connections to exceed before a warning Alert is fired.
*/
private int warningSize;
/**
* The time a thread will wait for a connection to become available.
*/
private final int waitTimeoutMillis;
/**
* The size of the preparedStatement cache;
*/
private int pstmtCacheSize;
private final PooledConnectionQueue queue;
private final Timer heartBeatTimer;
/**
* Used to find and close() leaked connections. Leaked connections are
* thought to be busy but have not been used for some time. Each time a
* connection is used it sets it's lastUsedTime.
*/
private long leakTimeMinutes;
public ConnectionPool(String name, DataSourceConfig params) {
this.config = params;
this.name = name;
this.notify = params.getAlert();
this.poolListener = params.getListener();
this.autoCommit = params.isAutoCommit();
this.readOnly = params.isReadOnly();
this.failOnStart = params.isFailOnStart();
this.initSql = params.getInitSql();
this.transactionIsolation = params.getIsolationLevel();
this.maxInactiveMillis = 1000 * params.getMaxInactiveTimeSecs();
this.maxAgeMillis = 60000 * params.getMaxAgeMinutes();
this.leakTimeMinutes = params.getLeakTimeMinutes();
this.captureStackTrace = params.isCaptureStackTrace();
this.maxStackTraceSize = params.getMaxStackTraceSize();
this.databaseDriver = params.getDriver();
this.databaseUrl = params.getUrl();
this.pstmtCacheSize = params.getPstmtCacheSize();
this.minConnections = params.getMinConnections();
this.maxConnections = params.getMaxConnections();
this.waitTimeoutMillis = params.getWaitTimeoutMillis();
this.heartbeatsql = params.getHeartbeatSql();
this.heartbeatFreqSecs = params.getHeartbeatFreqSecs();
this.heartbeatTimeoutSeconds = params.getHeartbeatTimeoutSeconds();
this.trimPoolFreqMillis = 1000 * params.getTrimPoolFreqSecs();
queue = new PooledConnectionQueue(this);
String un = params.getUsername();
String pw = params.getPassword();
if (un == null) {
throw new DataSourceConfigurationException("DataSource user is null?");
}
if (pw == null) {
throw new DataSourceConfigurationException("DataSource password is null?");
}
this.connectionProps = new Properties();
this.connectionProps.setProperty("user", un);
this.connectionProps.setProperty("password", pw);
Map customProperties = params.getCustomProperties();
if (customProperties != null) {
Set> entrySet = customProperties.entrySet();
for (Entry entry : entrySet) {
this.connectionProps.setProperty(entry.getKey(), entry.getValue());
}
}
try {
initialise();
int freqMillis = heartbeatFreqSecs * 1000;
heartBeatTimer = new Timer(name + ".heartBeat", true);
if (freqMillis > 0) {
heartBeatTimer.scheduleAtFixedRate(new HeartBeatRunnable(), freqMillis, freqMillis);
}
} catch (SQLException ex) {
throw new DataSourceInitialiseException("Error initialising DataSource", ex);
}
}
class HeartBeatRunnable extends TimerTask {
@Override
public void run() {
checkDataSource();
}
}
@Override
public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
throw new SQLFeatureNotSupportedException("We do not support java.util.logging");
}
private void initialise() throws SQLException {
// Ensure database driver is loaded
try {
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
if (contextLoader != null) {
Class.forName(databaseDriver, true, contextLoader);
} else {
Class.forName(databaseDriver, true, this.getClass().getClassLoader());
}
} catch (Throwable e) {
throw new IllegalStateException("Problem loading Database Driver [" + this.databaseDriver + "]: " + e.getMessage(), e);
}
String transIsolation = TransactionIsolation.getDescription(transactionIsolation);
//noinspection StringBufferReplaceableByString
StringBuilder sb = new StringBuilder(70);
sb.append("DataSourcePool [").append(name);
sb.append("] autoCommit[").append(autoCommit);
sb.append("] transIsolation[").append(transIsolation);
sb.append("] min[").append(minConnections);
sb.append("] max[").append(maxConnections).append("]");
logger.info(sb.toString());
if (config.useInitDatabase()) {
initialiseDatabase();
}
try {
queue.ensureMinimumConnections();
} catch (SQLException e) {
if (failOnStart) {
throw e;
}
logger.error("Error trying to ensure minimum connections. Maybe db server is down.", e);
}
}
/**
* Initialise the database using the owner credentials if we can't connect using the normal credentials.
*
* That is, if we think the username doesn't exist in the DB, initialise the DB using the owner credentials.
*
*/
private void initialiseDatabase() throws SQLException {
try (Connection connection = createUnpooledConnection(connectionProps, false)) {
// successfully obtained a connection so skip initDatabase
connection.clearWarnings();
} catch (SQLException e) {
// expected when user does not exists, obtain a connection using owner credentials
try (Connection connection = createUnpooledConnection(config.getOwnerUsername(), config.getOwnerPassword())) {
// initialise the DB (typically create the user/role using the owner credentials etc)
InitDatabase initDatabase = config.getInitDatabase();
initDatabase.run(connection, config);
connection.commit();
}
}
}
/**
* Returns false.
*/
@Override
public boolean isWrapperFor(Class> arg0) throws SQLException {
return false;
}
/**
* Not Implemented.
*/
@Override
public T unwrap(Class arg0) throws SQLException {
throw new SQLException("Not Implemented");
}
/**
* Return the dataSource name.
*/
@Override
public String getName() {
return name;
}
/**
* Return the max size of stack traces used when trying to find connection pool leaks.
*
* This is only used when {@link #isCaptureStackTrace()} is true.
*
*/
int getMaxStackTraceSize() {
return maxStackTraceSize;
}
/**
* Returns false when the dataSource is down.
*/
@Override
public boolean isDataSourceUp() {
return dataSourceUp;
}
@Override
public SQLException getDataSourceDownReason() {
return dataSourceDownReason;
}
/**
* Called when the pool hits the warning level.
*/
protected void notifyWarning(String msg) {
if (inWarningMode.compareAndSet(false, true)) {
// send an Error to the event log...
logger.warn(msg);
if (notify != null) {
notify.dataSourceWarning(this, msg);
}
}
}
private synchronized void notifyDataSourceIsDown(SQLException ex) {
if (dataSourceUp) {
reset();
}
dataSourceUp = false;
if (ex != null) {
dataSourceDownReason = ex;
}
if (!dataSourceDownAlertSent) {
dataSourceDownAlertSent = true;
logger.error("FATAL: DataSourcePool [" + name + "] is down or has network error!!!", ex);
if (notify != null) {
notify.dataSourceDown(this, ex);
}
}
}
private synchronized void notifyDataSourceIsUp() {
if (dataSourceDownAlertSent) {
// set to false here, so that a getConnection() call in DataSourceAlert.dataSourceUp
// in same thread does not fire the event again (and end in recursion)
// all other threads will be blocked, becasue method is synchronized.
dataSourceDownAlertSent = false;
logger.error("RESOLVED FATAL: DataSourcePool [" + name + "] is back up!");
if (notify != null) {
notify.dataSourceUp(this);
}
} else if (!dataSourceUp) {
logger.info("DataSourcePool [" + name + "] is back up!");
}
if (!dataSourceUp) {
dataSourceUp = true;
dataSourceDownReason = null;
reset();
}
}
/**
* Trim connections (in the free list) based on idle time and maximum age.
*/
private void trimIdleConnections() {
if (System.currentTimeMillis() > (lastTrimTime + trimPoolFreqMillis)) {
try {
queue.trim(maxInactiveMillis, maxAgeMillis);
lastTrimTime = System.currentTimeMillis();
} catch (Exception e) {
logger.error("Error trying to trim idle connections", e);
}
}
}
/**
* Check the dataSource is up. Trim connections.
*
* This is called by the HeartbeatRunnable which should be scheduled to
* run periodically (every heartbeatFreqSecs seconds actually).
*
*/
private void checkDataSource() {
// first trim idle connections
trimIdleConnections();
Connection conn = null;
try {
// Get a connection from the pool and test it
conn = getConnection();
if (testConnection(conn)) {
notifyDataSourceIsUp();
} else {
notifyDataSourceIsDown(null);
}
} catch (SQLException ex) {
notifyDataSourceIsDown(ex);
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (SQLException ex) {
logger.warn("Can't close connection in checkDataSource!");
}
}
}
/**
* Initializes the connection we got from the driver.
*/
private void initConnection(Connection conn) throws SQLException {
conn.setAutoCommit(autoCommit);
// isolation level is set globally for all connections (at least for H2)
// and you will need admin rights - so we do not change it, if it already
// matches.
if (conn.getTransactionIsolation() != transactionIsolation) {
conn.setTransactionIsolation(transactionIsolation);
}
if (readOnly) {
conn.setReadOnly(readOnly);
}
if (initSql != null) {
for (String query : initSql) {
try (Statement stmt = conn.createStatement()) {
stmt.execute(query);
}
}
}
}
/**
* Create an un-pooled connection with the given username and password.
*/
public Connection createUnpooledConnection(String username, String password) throws SQLException {
Properties properties = new Properties(connectionProps);
properties.setProperty("user", username);
properties.setProperty("password", password);
return createUnpooledConnection(properties, true);
}
/**
* Create an un-pooled connection.
*/
public Connection createUnpooledConnection() throws SQLException {
return createUnpooledConnection(connectionProps, true);
}
private Connection createUnpooledConnection(Properties properties, boolean notifyIsDown) throws SQLException {
try {
Connection conn = DriverManager.getConnection(databaseUrl, properties);
initConnection(conn);
return conn;
} catch (SQLException ex) {
if (notifyIsDown) {
notifyDataSourceIsDown(null);
}
throw ex;
}
}
/**
* Set a new maximum size. The pool should respect this new maximum
* immediately and not require a restart. You may want to increase the
* maxConnections if the pool gets large and hits the warning level.
*/
@Override
public void setMaxSize(int max) {
queue.setMaxSize(max);
this.maxConnections = max;
}
/**
* Return the max size this pool can grow to.
*/
public int getMaxSize() {
return maxConnections;
}
/**
* Set the min size this pool should maintain.
*/
public void setMinSize(int min) {
queue.setMinSize(min);
this.minConnections = min;
}
/**
* Return the min size this pool should maintain.
*/
public int getMinSize() {
return minConnections;
}
/**
* Set a new maximum size. The pool should respect this new maximum
* immediately and not require a restart. You may want to increase the
* maxConnections if the pool gets large and hits the warning and or alert
* levels.
*/
@Override
public void setWarningSize(int warningSize) {
queue.setWarningSize(warningSize);
this.warningSize = warningSize;
}
/**
* Return the warning size. When the pool hits this size it can send a
* notify message to an administrator.
*/
@Override
public int getWarningSize() {
return warningSize;
}
/**
* Return the time in millis that threads will wait when the pool has hit
* the max size. These threads wait for connections to be returned by the
* busy connections.
*/
public int getWaitTimeoutMillis() {
return waitTimeoutMillis;
}
/**
* Return the time after which inactive connections are trimmed.
*/
public int getMaxInactiveMillis() {
return maxInactiveMillis;
}
/**
* Return the maximum age a connection is allowed to be before it is trimmed
* out of the pool. This value can be 0 which means there is no maximum age.
*/
public long getMaxAgeMillis() {
return maxAgeMillis;
}
private boolean testConnection(Connection conn) throws SQLException {
if (heartbeatsql == null) {
return conn.isValid(heartbeatTimeoutSeconds);
}
Statement stmt = null;
ResultSet rset = null;
try {
// It should only error IF the DataSource is down or a network issue
stmt = conn.createStatement();
if (heartbeatTimeoutSeconds > 0) {
stmt.setQueryTimeout(heartbeatTimeoutSeconds);
}
rset = stmt.executeQuery(heartbeatsql);
conn.commit();
return true;
} finally {
try {
if (rset != null) {
rset.close();
}
} catch (SQLException e) {
logger.error(null, e);
}
try {
if (stmt != null) {
stmt.close();
}
} catch (SQLException e) {
logger.error(null, e);
}
}
}
/**
* Make sure the connection is still ok to use. If not then remove it from
* the pool.
*/
boolean validateConnection(PooledConnection conn) {
try {
return testConnection(conn);
} catch (Exception e) {
logger.warn("heartbeatsql test failed on connection[" + conn.getName() + "]");
return false;
}
}
/**
* Called by the PooledConnection themselves, returning themselves to the
* pool when they have been finished with.
*
* Note that connections may not be added back to the pool if returnToPool
* is false or if they where created before the recycleTime. In both of
* these cases the connection is fully closed and not pooled.
*
*
* @param pooledConnection the returning connection
*/
void returnConnection(PooledConnection pooledConnection) {
// return a normal 'good' connection
returnTheConnection(pooledConnection, false);
}
/**
* This is a bad connection and must be removed from the pool's busy list and fully closed.
*/
void returnConnectionForceClose(PooledConnection pooledConnection) {
returnTheConnection(pooledConnection, true);
}
/**
* Return connection. If forceClose is true then this is a bad connection that
* must be removed and closed fully.
*/
private void returnTheConnection(PooledConnection pooledConnection, boolean forceClose) {
if (poolListener != null && !forceClose) {
poolListener.onBeforeReturnConnection(pooledConnection);
}
queue.returnPooledConnection(pooledConnection, forceClose);
if (forceClose) {
// Got a bad connection so check the pool
checkDataSource();
}
}
/**
* Collect statistics of a connection that is fully closing
*/
void reportClosingConnection(PooledConnection pooledConnection) {
queue.reportClosingConnection(pooledConnection);
}
/**
* Returns information describing connections that are currently being used.
*/
public String getBusyConnectionInformation() {
return queue.getBusyConnectionInformation();
}
/**
* Dumps the busy connection information to the logs.
*
* This includes the stackTrace elements if they are being captured. This is
* useful when needing to look a potential connection pool leaks.
*
*/
public void dumpBusyConnectionInformation() {
queue.dumpBusyConnectionInformation();
}
/**
* Close any busy connections that have not been used for some time.
*
* These connections are considered to have leaked from the connection pool.
*
*
* Connection leaks occur when code doesn't ensure that connections are
* closed() after they have been finished with. There should be an
* appropriate try catch finally block to ensure connections are always
* closed and put back into the pool.
*
*/
public void closeBusyConnections(long leakTimeMinutes) {
queue.closeBusyConnections(leakTimeMinutes);
}
/**
* Grow the pool by creating a new connection. The connection can either be
* added to the available list, or returned.
*
* This method is protected by synchronization in calling methods.
*
*/
PooledConnection createConnectionForQueue(int connId) throws SQLException {
try {
Connection c = createUnpooledConnection();
PooledConnection pc = new PooledConnection(this, connId, c);
pc.resetForUse();
if (!dataSourceUp) {
notifyDataSourceIsUp();
}
return pc;
} catch (SQLException ex) {
notifyDataSourceIsDown(ex);
throw ex;
}
}
/**
* Close all the connections in the pool.
*
*
* - Checks that the database is up.
*
- Resets the Alert level.
*
- Closes busy connections that have not been used for some time (aka
* leaks).
*
- This closes all the currently available connections.
*
- Busy connections are closed when they are returned to the pool.
*
*
*/
public void reset() {
queue.reset(leakTimeMinutes);
inWarningMode.set(false);
}
/**
* Return a pooled connection.
*/
@Override
public Connection getConnection() throws SQLException {
return getPooledConnection();
}
/**
* Get a connection from the pool.
*
* This will grow the pool if all the current connections are busy. This
* will go into a wait if the pool has hit its maximum size.
*
*/
private PooledConnection getPooledConnection() throws SQLException {
PooledConnection c = queue.getPooledConnection();
if (captureStackTrace) {
c.setStackTrace(Thread.currentThread().getStackTrace());
}
if (poolListener != null) {
poolListener.onAfterBorrowConnection(c);
}
return c;
}
/**
* Send a message to the DataSourceAlertListener to test it. This is so that
* you can make sure the alerter is configured correctly etc.
*/
public void testAlert() {
String msg = "Just testing if alert message is sent successfully.";
if (notify != null) {
notify.dataSourceWarning(this, msg);
}
}
/**
* This will close all the free connections, and then go into a wait loop,
* waiting for the busy connections to be freed.
*
*
* The DataSources's should be shutdown AFTER thread pools. Leaked
* Connections are not waited on, as that would hang the server.
*
*/
@Override
public void shutdown(boolean deregisterDriver) {
heartBeatTimer.cancel();
queue.shutdown();
if (deregisterDriver) {
deregisterDriver();
}
}
/**
* Return the default autoCommit setting Connections in this pool will use.
*
* @return true if the pool defaults autoCommit to true
*/
@Override
public boolean isAutoCommit() {
return autoCommit;
}
/**
* Return the default transaction isolation level connections in this pool
* should have.
*
* @return the default transaction isolation level
*/
int getTransactionIsolation() {
return transactionIsolation;
}
/**
* Return true if the connection pool is currently capturing the StackTrace
* when connections are 'got' from the pool.
*
* This is set to true to help diagnose connection pool leaks.
*
*/
public boolean isCaptureStackTrace() {
return captureStackTrace;
}
/**
* Set this to true means that the StackElements are captured every time a
* connection is retrieved from the pool. This can be used to identify
* connection pool leaks.
*/
public void setCaptureStackTrace(boolean captureStackTrace) {
this.captureStackTrace = captureStackTrace;
}
/**
* Create an un-pooled connection with the given username and password.
*
* This uses the default isolation level and autocommit mode.
*/
@Override
public Connection getConnection(String username, String password) throws SQLException {
Properties props = new Properties();
props.putAll(connectionProps);
props.setProperty("user", username);
props.setProperty("password", password);
Connection conn = DriverManager.getConnection(databaseUrl, props);
initConnection(conn);
return conn;
}
/**
* Not implemented and shouldn't be used.
*/
@Override
public int getLoginTimeout() throws SQLException {
throw new SQLException("Method not supported");
}
/**
* Not implemented and shouldn't be used.
*/
@Override
public void setLoginTimeout(int seconds) throws SQLException {
throw new SQLException("Method not supported");
}
/**
* Returns null.
*/
@Override
public PrintWriter getLogWriter() {
return null;
}
/**
* Not implemented.
*/
@Override
public void setLogWriter(PrintWriter writer) throws SQLException {
throw new SQLException("Method not supported");
}
/**
* For detecting and closing leaked connections. Connections that have been
* busy for more than leakTimeMinutes are considered leaks and will be
* closed on a reset().
*
* If you want to use a connection for that longer then you should consider
* creating an unpooled connection or setting longRunning to true on that
* connection.
*
*/
public void setLeakTimeMinutes(long leakTimeMinutes) {
this.leakTimeMinutes = leakTimeMinutes;
}
/**
* Return the number of minutes after which a busy connection could be
* considered leaked from the connection pool.
*/
public long getLeakTimeMinutes() {
return leakTimeMinutes;
}
/**
* Return the preparedStatement cache size.
*/
public int getPstmtCacheSize() {
return pstmtCacheSize;
}
/**
* Set the preparedStatement cache size.
*/
public void setPstmtCacheSize(int pstmtCacheSize) {
this.pstmtCacheSize = pstmtCacheSize;
}
/**
* Return the current status of the connection pool.
*
* If you pass reset = true then the counters such as
* hitCount, waitCount and highWaterMark are reset.
*
*/
@Override
public PoolStatus getStatus(boolean reset) {
return queue.getStatus(reset);
}
/**
* Return the aggregated load statistics collected on all the connections in the pool.
*/
@Override
public PoolStatistics getStatistics(boolean reset) {
return queue.getStatistics(reset);
}
/**
* Deregister the JDBC driver.
*/
private void deregisterDriver() {
try {
logger.debug("Deregister the JDBC driver " + this.databaseDriver);
DriverManager.deregisterDriver(DriverManager.getDriver(this.databaseUrl));
} catch (SQLException e) {
logger.warn("Error trying to deregister the JDBC driver " + this.databaseDriver, e);
}
}
public static class Status implements PoolStatus {
private final int minSize;
private final int maxSize;
private final int free;
private final int busy;
private final int waiting;
private final int highWaterMark;
private final int waitCount;
private final int hitCount;
protected Status(int minSize, int maxSize, int free, int busy, int waiting, int highWaterMark, int waitCount, int hitCount) {
this.minSize = minSize;
this.maxSize = maxSize;
this.free = free;
this.busy = busy;
this.waiting = waiting;
this.highWaterMark = highWaterMark;
this.waitCount = waitCount;
this.hitCount = hitCount;
}
public String toString() {
return "min[" + minSize + "] max[" + maxSize + "] free[" + free + "] busy[" + busy + "] waiting[" + waiting
+ "] highWaterMark[" + highWaterMark + "] waitCount[" + waitCount + "] hitCount[" + hitCount + "]";
}
/**
* Return the min pool size.
*/
@Override
public int getMinSize() {
return minSize;
}
/**
* Return the max pool size.
*/
@Override
public int getMaxSize() {
return maxSize;
}
/**
* Return the current number of free connections in the pool.
*/
@Override
public int getFree() {
return free;
}
/**
* Return the current number of busy connections in the pool.
*/
@Override
public int getBusy() {
return busy;
}
/**
* Return the current number of threads waiting for a connection.
*/
@Override
public int getWaiting() {
return waiting;
}
/**
* Return the high water mark of busy connections.
*/
@Override
public int getHighWaterMark() {
return highWaterMark;
}
/**
* Return the total number of times a thread had to wait.
*/
@Override
public int getWaitCount() {
return waitCount;
}
/**
* Return the total number of times there was an attempt to get a
* connection.
*
* If the attempt to get a connection failed with a timeout or other
* exception those attempts are still included in this hit count.
*
*/
@Override
public int getHitCount() {
return hitCount;
}
}
}