Merge branch 'dev' into experimental

pull/458/head
Brett Wooldridge 9 years ago
commit eee453a873

@ -1,5 +1,29 @@
HikariCP Changes
Changes in 2.4.2
* Improve accuracy of timeouts for getConnection() calls by accounting for possibly
long delay aliveness tests.
* Improve adherence to minimumIdle goal by closing idle connections starting from
longest idle time to shortest. Additionally, stop when minimumIdle is reached even
if connections exceeding idleTimeout remain (but are still within maxLifetime).
* Ongoing com.zaxxer.hikari.metrics refactors. This is not considered public API until
such time as we announce it. Caveat lector.
* Performance improvements in the getConnection()/close() hot path.
* issue 415: remove use of java.beans classes to allow use of HikariCP with the
Zulu JRE compact3 profile.
* issue 406: execute validation query during connection setup to make sure it is
valid SQL.
* Fixed issue with proxy generation whereby the generated classes contain the
major version number for Java 8, which makes them incompatible with the Java 7
runtime.
Changes in 2.4.1
* issue 380: housekeeper was not being scheduled in the case of a user specified
@ -43,7 +67,7 @@ Changes in 2.4.0
@Deprecated have been removed.
* Deprecated HikariDataSource.shutdown() in favor of close().
* Improve shutdown performance.
* Allow user specified ScheduledThreadPoolExecutor for housekeeping timer. Useful
@ -58,7 +82,7 @@ Changes in 2.3.7
* Allow a specifically set DataSource instance to override other settings such as jdbcUrl,
dataSourceClassName, or driverClassName.
* Fixed issue where, in the case of a driver-based configuration (jdbcUrl), we were not
initialising the network timeout Executor.

@ -3,7 +3,7 @@
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.4.2-SNAPSHOT</version>
<version>2.4.2-RC1</version>
<packaging>bundle</packaging>
<name>HikariCP</name>

@ -763,6 +763,9 @@ public class HikariConfig implements HikariConfigMXBean
LOGGER.error("cannot use driverClassName and dataSourceClassName together");
throw new IllegalArgumentException("cannot use driverClassName and dataSourceClassName together");
}
else if (jdbcUrl != null && dataSourceClassName != null) {
LOGGER.warn("using dataSourceClassName and ignoring jdbcUrl");
}
else if (jdbcUrl != null) {
// OK
}

@ -67,7 +67,7 @@ public class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateL
private static final ClockSource clockSource = ClockSource.INSTANCE;
private static final long ALIVE_BYPASS_WINDOW_MS = Long.getLong("com.zaxxer.hikari.aliveBypassWindow", TimeUnit.SECONDS.toMillis(1));
private static final long ALIVE_BYPASS_WINDOW_MS = Long.getLong("com.zaxxer.hikari.aliveBypassWindowMs", TimeUnit.MILLISECONDS.toMillis(500));
private static final long HOUSEKEEPING_PERIOD_MS = Long.getLong("com.zaxxer.hikari.housekeeping.periodMs", TimeUnit.SECONDS.toMillis(30));
private static final int POOL_NORMAL = 0;
@ -95,7 +95,7 @@ public class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateL
* @param config a HikariConfig instance
*/
public HikariPool(final HikariConfig config)
{
{
super(config);
this.connectionBag = new ConcurrentBag<>(this);
@ -165,16 +165,15 @@ public class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateL
}
final long now = clockSource.currentTime();
if (poolEntry.evict || (clockSource.elapsedMillis(poolEntry.lastAccessed, now) > ALIVE_BYPASS_WINDOW_MS && !isConnectionAlive(poolEntry.connection))) {
if (poolEntry.isMarkedEvicted() || (clockSource.elapsedMillis(poolEntry.lastAccessed, now) > ALIVE_BYPASS_WINDOW_MS && !isConnectionAlive(poolEntry.connection))) {
closeConnection(poolEntry, "(connection evicted or dead)"); // Throw away the dead connection and try again
timeout = hardTimeout - clockSource.elapsedMillis(startTime, now);
timeout = hardTimeout - clockSource.elapsedMillis(startTime);
}
else {
metricsTracker.recordBorrowStats(poolEntry, startTime);
return poolEntry.createProxyConnection(leakTask.start(poolEntry), now);
}
}
while (timeout > 0L);
} while (timeout > 0L);
}
catch (InterruptedException e) {
throw new SQLException(poolName + " - Interrupted during connection acquisition", e);
@ -197,23 +196,6 @@ public class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateL
throw connectionException;
}
/**
* Release a connection back to the pool, or permanently close it if it is broken.
*
* @param poolEntry the PoolBagEntry to release back to the pool
*/
public final void releaseConnection(final PoolEntry poolEntry)
{
metricsTracker.recordConnectionUsage(poolEntry);
if (poolEntry.evict) {
closeConnection(poolEntry, "(connection broken or evicted)");
}
else {
connectionBag.requite(poolEntry);
}
}
/**
* Shutdown the pool, closing all idle connections and aborting or closing
* active connections.
@ -244,9 +226,9 @@ public class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateL
do {
softEvictConnections();
abortActiveConnections(assassinExecutor);
}
while (getTotalConnections() > 0 && clockSource.elapsedMillis(start) < TimeUnit.SECONDS.toMillis(5));
} finally {
} while (getTotalConnections() > 0 && clockSource.elapsedMillis(start) < TimeUnit.SECONDS.toMillis(5));
}
finally {
assassinExecutor.shutdown();
assassinExecutor.awaitTermination(5L, TimeUnit.SECONDS);
}
@ -302,20 +284,6 @@ public class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateL
}
}
/**
* Log the current pool state at debug level.
*
* @param prefix an optional prefix to prepend the log message
*/
public final void logPoolState(String... prefix)
{
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("{}pool {} stats (total={}, active={}, idle={}, waiting={})",
(prefix.length > 0 ? prefix[0] : ""), poolName,
getTotalConnections(), getActiveConnections(), getIdleConnections(), getThreadsAwaitingConnection());
}
}
// ***********************************************************************
// IBagStateListener callback
// ***********************************************************************
@ -334,7 +302,7 @@ public class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateL
while (poolState == POOL_NORMAL && totalConnections.get() < maxPoolSize && getIdleConnections() <= minimumIdle && !addConnection()) {
// If we got into the loop, addConnection() failed, so we sleep and retry
quietlySleep(sleepBackoff);
sleepBackoff = Math.min(connectionTimeout / 2, (long) (sleepBackoff * 1.5));
sleepBackoff = Math.min(connectionTimeout / 2, (long) (sleepBackoff * 1.3));
}
}
}, true);
@ -412,12 +380,38 @@ public class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateL
// Package methods
// ***********************************************************************
/**
* Log the current pool state at debug level.
*
* @param prefix an optional prefix to prepend the log message
*/
final void logPoolState(String... prefix)
{
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("{}pool {} stats (total={}, active={}, idle={}, waiting={})",
(prefix.length > 0 ? prefix[0] : ""), poolName,
getTotalConnections(), getActiveConnections(), getIdleConnections(), getThreadsAwaitingConnection());
}
}
/**
* Release a connection back to the pool, or permanently close it if it is broken.
*
* @param poolEntry the PoolBagEntry to release back to the pool
*/
final void releaseConnection(final PoolEntry poolEntry)
{
metricsTracker.recordConnectionUsage(poolEntry);
connectionBag.requite(poolEntry);
}
/**
* Permanently close the real (underlying) connection (eat any exception).
*
* @param poolEntry the connection to actually close
*/
void closeConnection(final PoolEntry poolEntry, final String closureReason)
final void closeConnection(final PoolEntry poolEntry, final String closureReason)
{
final Connection connection = poolEntry.connection;
poolEntry.close();
@ -448,6 +442,7 @@ public class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateL
// Speculative increment of totalConnections with expectation of success
if (totalConnections.incrementAndGet() > config.getMaximumPoolSize()) {
totalConnections.decrementAndGet(); // Pool is maxed out, so undo speculative increment of totalConnections
//LOGGER.debug("{} - Cannot exceed maximum connections capacity: {}", poolName, config.getMaximumPoolSize());
return true;
}
@ -493,7 +488,7 @@ public class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateL
addConnectionExecutor.execute(new Runnable() {
@Override
public void run() {
logPoolState("After fill\t");
logPoolState("After adding\t");
}
});
}
@ -506,7 +501,6 @@ public class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateL
{
for (PoolEntry poolEntry : connectionBag.values(STATE_IN_USE)) {
try {
poolEntry.evict = true;
poolEntry.connection.abort(assassinExecutor);
}
catch (Throwable e) {
@ -534,7 +528,7 @@ public class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateL
final PoolEntry poolEntry = connectionBag.borrow(connectionTimeout, TimeUnit.MILLISECONDS);
if (config.getMinimumIdle() == 0) {
closeConnection(poolEntry, "Initialization validation complete, closing test connection.");
closeConnection(poolEntry, "Closing connection borrowed for validation.");
}
else {
connectionBag.requite(poolEntry);
@ -557,10 +551,12 @@ public class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateL
private void softEvictConnection(final PoolEntry poolEntry, final String reason, final boolean owner)
{
poolEntry.evict();
if (connectionBag.reserve(poolEntry) || owner) {
if (owner || connectionBag.reserve(poolEntry)) {
closeConnection(poolEntry, reason);
}
else {
poolEntry.markEvicted();
}
}
private PoolStats getPoolStats()
@ -617,16 +613,17 @@ public class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateL
// Sort pool entries on lastAccessed
Collections.sort(notInUseList, PoolEntry.LASTACCESS_COMPARABLE);
// Iterate the first N removable elements
for (final Iterator<PoolEntry> iter = notInUseList.iterator(); removable > 0 && iter.hasNext(); ) {
final Iterator<PoolEntry> iter = notInUseList.iterator();
do {
final PoolEntry poolEntry = iter.next();
if (clockSource.elapsedMillis(poolEntry.lastAccessed, now) > idleTimeout && connectionBag.reserve(poolEntry)) {
closeConnection(poolEntry, "(connection passed idleTimeout)");
removable--;
}
}
} while (removable > 0 && iter.hasNext());
}
}
logPoolState("After cleanup\t");
fillPool(); // Try to maintain minimum connections

@ -1,6 +1,11 @@
package com.zaxxer.hikari.pool;
import static com.zaxxer.hikari.util.UtilityElf.createInstance;
import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_CATALOG;
import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_READONLY;
import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_ISOLATION;
import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_AUTOCOMMIT;
import static com.zaxxer.hikari.pool.ProxyConnection.DIRTY_BIT_NETTIMEOUT;
import java.lang.management.ManagementFactory;
import java.sql.Connection;
@ -94,11 +99,11 @@ abstract class PoolBase
void quietlyCloseConnection(final Connection connection, final String closureReason)
{
try {
if (connection == null || connection.isClosed()) {
return;
}
if (connection == null) {
return;
}
try {
LOGGER.debug("{} - Closing connection {}: {}", poolName, connection, closureReason);
try {
setNetworkTimeout(connection, TimeUnit.SECONDS.toMillis(15));
@ -128,10 +133,10 @@ abstract class PoolBase
if (isNetworkTimeoutSupported != TRUE) {
setQueryTimeout(statement, (int) TimeUnit.MILLISECONDS.toSeconds(validationTimeout));
}
statement.execute(config.getConnectionTestQuery());
}
if (isIsolateInternalQueries && !isReadOnly && !isAutoCommit) {
connection.rollback();
}
@ -163,39 +168,39 @@ abstract class PoolBase
PoolEntry newPoolEntry() throws Exception
{
return new PoolEntry(newConnection(), this);
return new PoolEntry(newConnection(), this, isReadOnly, isAutoCommit);
}
void resetConnectionState(final Connection connection, final ProxyConnection proxyConnection, final int dirtyBits) throws SQLException
{
int resetBits = 0;
if ((dirtyBits & 0b00001) != 0 && proxyConnection.getReadOnlyState() != isReadOnly) {
if ((dirtyBits & DIRTY_BIT_READONLY) != 0 && proxyConnection.getReadOnlyState() != isReadOnly) {
connection.setReadOnly(isReadOnly);
resetBits |= 0b00001;
resetBits |= DIRTY_BIT_READONLY;
}
if ((dirtyBits & 0b00010) != 0 && proxyConnection.getAutoCommitState() != isAutoCommit) {
if ((dirtyBits & DIRTY_BIT_AUTOCOMMIT) != 0 && proxyConnection.getAutoCommitState() != isAutoCommit) {
connection.setAutoCommit(isAutoCommit);
resetBits |= 0b00010;
resetBits |= DIRTY_BIT_AUTOCOMMIT;
}
if ((dirtyBits & 0b00100) != 0 && proxyConnection.getTransactionIsolationState() != transactionIsolation) {
if ((dirtyBits & DIRTY_BIT_ISOLATION) != 0 && proxyConnection.getTransactionIsolationState() != transactionIsolation) {
connection.setTransactionIsolation(transactionIsolation);
resetBits |= 0b00100;
resetBits |= DIRTY_BIT_ISOLATION;
}
if ((dirtyBits & 0b01000) != 0) {
if ((dirtyBits & DIRTY_BIT_CATALOG) != 0) {
final String currentCatalog = proxyConnection.getCatalogState();
if ((currentCatalog != null && !currentCatalog.equals(catalog)) || (currentCatalog == null && catalog != null)) {
connection.setCatalog(catalog);
resetBits |= 0b01000;
resetBits |= DIRTY_BIT_CATALOG;
}
}
if ((dirtyBits & 0b10000) != 0 && proxyConnection.getNetworkTimeoutState() != networkTimeout) {
if ((dirtyBits & DIRTY_BIT_NETTIMEOUT) != 0 && proxyConnection.getNetworkTimeoutState() != networkTimeout) {
setNetworkTimeout(connection, networkTimeout);
resetBits |= 0b10000;
resetBits |= DIRTY_BIT_NETTIMEOUT;
}
if (LOGGER.isDebugEnabled()) {
@ -461,6 +466,7 @@ abstract class PoolBase
if (sql != null) {
try (Statement statement = connection.createStatement()) {
//con created few ms before, set query timeout is omitted
statement.execute(sql);
if (!isReadOnly) {
@ -519,7 +525,7 @@ abstract class PoolBase
try {
dataSource.setLoginTimeout((int) TimeUnit.MILLISECONDS.toSeconds(Math.max(1000L, connectionTimeout)));
}
catch (SQLException e) {
catch (Throwable e) {
LOGGER.warn("{} - Unable to set DataSource login timeout", poolName, e);
}
}

@ -42,13 +42,16 @@ final class PoolEntry implements IConcurrentBagEntry
Connection connection;
long lastAccessed;
long lastBorrowed;
volatile boolean evict;
private volatile boolean evict;
private volatile ScheduledFuture<?> endOfLife;
private final FastList<Statement> openStatements;
private final PoolBase poolBase;
private final HikariPool hikariPool;
private final AtomicInteger state;
private volatile ScheduledFuture<?> endOfLife;
private final boolean isReadOnly;
private final boolean isAutoCommit;
static
{
@ -60,13 +63,15 @@ final class PoolEntry implements IConcurrentBagEntry
};
}
PoolEntry(final Connection connection, final PoolBase pool)
PoolEntry(final Connection connection, final PoolBase pool, final boolean isReadOnly, final boolean isAutoCommit)
{
this.connection = connection;
this.poolBase = pool;
this.hikariPool = (HikariPool) pool;
this.state = new AtomicInteger(STATE_NOT_IN_USE);
this.lastAccessed = ClockSource.INSTANCE.currentTime();
this.openStatements = new FastList<>(Statement.class, 16);
this.isReadOnly = isReadOnly;
this.isAutoCommit = isAutoCommit;
}
/**
@ -77,7 +82,7 @@ final class PoolEntry implements IConcurrentBagEntry
void recycle(final long lastAccessed)
{
this.lastAccessed = lastAccessed;
poolBase.releaseConnection(this);
hikariPool.releaseConnection(this);
}
/**
@ -90,37 +95,32 @@ final class PoolEntry implements IConcurrentBagEntry
Connection createProxyConnection(final ProxyLeakTask leakTask, final long now)
{
return ProxyFactory.getProxyConnection(this, connection, openStatements, leakTask, now);
return ProxyFactory.getProxyConnection(this, connection, openStatements, leakTask, now, isReadOnly, isAutoCommit);
}
void resetConnectionState(final ProxyConnection proxyConnection, final int dirtyBits) throws SQLException
{
poolBase.resetConnectionState(connection, proxyConnection, dirtyBits);
hikariPool.resetConnectionState(connection, proxyConnection, dirtyBits);
}
String getPoolName()
{
return poolBase.toString();
}
Connection getConnection()
{
return connection;
return hikariPool.toString();
}
boolean isEvicted()
boolean isMarkedEvicted()
{
return evict;
}
void evict()
void markEvicted()
{
this.evict = true;
}
FastList<Statement> getStatementsList()
void evict(final String closureReason)
{
return openStatements;
hikariPool.closeConnection(this, closureReason);
}
/** Returns millis since lastBorrowed */

@ -43,25 +43,31 @@ import com.zaxxer.hikari.util.FastList;
*/
public abstract class ProxyConnection implements Connection
{
static final int DIRTY_BIT_READONLY = 0b00001;
static final int DIRTY_BIT_AUTOCOMMIT = 0b00010;
static final int DIRTY_BIT_ISOLATION = 0b00100;
static final int DIRTY_BIT_CATALOG = 0b01000;
static final int DIRTY_BIT_NETTIMEOUT = 0b10000;
private static final Logger LOGGER;
private static final Set<String> SQL_ERRORS;
private static final ClockSource clockSource;
protected Connection delegate;
private final ProxyLeakTask leakTask;
private final PoolEntry poolEntry;
private final ProxyLeakTask leakTask;
private final FastList<Statement> openStatements;
private int dirtyBits;
private long lastAccess;
private boolean isCommitStateDirty;
private boolean isReadOnly;
private boolean isAutoCommit;
private int networkTimeout;
private int transactionIsolation;
private String dbcatalog;
private boolean isReadOnly;
// static initializer
static {
@ -77,12 +83,14 @@ public abstract class ProxyConnection implements Connection
SQL_ERRORS.add("JZ0C1"); // Sybase disconnect error
}
protected ProxyConnection(final PoolEntry poolEntry, final Connection connection, final FastList<Statement> openStatements, final ProxyLeakTask leakTask, final long now) {
protected ProxyConnection(final PoolEntry poolEntry, final Connection connection, final FastList<Statement> openStatements, final ProxyLeakTask leakTask, final long now, final boolean isReadOnly, final boolean isAutoCommit) {
this.poolEntry = poolEntry;
this.delegate = connection;
this.openStatements = openStatements;
this.leakTask = leakTask;
this.lastAccess = now;
this.isReadOnly = isReadOnly;
this.isAutoCommit = isAutoCommit;
}
/** {@inheritDoc} */
@ -137,16 +145,18 @@ public abstract class ProxyConnection implements Connection
/** {@inheritDoc} */
final SQLException checkException(final SQLException sqle)
{
String sqlState = sqle.getSQLState();
final String sqlState = sqle.getSQLState();
if (sqlState != null) {
boolean isForceClose = sqlState.startsWith("08") || SQL_ERRORS.contains(sqlState);
if (isForceClose) {
poolEntry.evict();
final boolean isForceClose = sqlState.startsWith("08") || SQL_ERRORS.contains(sqlState);
if (isForceClose && delegate != ClosedConnection.CLOSED_CONNECTION) {
LOGGER.warn("{} - Connection {} marked as broken because of SQLSTATE({}), ErrorCode({})",
poolEntry.getPoolName(), delegate, sqlState, sqle.getErrorCode(), sqle);
leakTask.cancel();
delegate = ClosedConnection.CLOSED_CONNECTION;
poolEntry.evict("(connection broken)");
}
else {
SQLException nse = sqle.getNextException();
final SQLException nse = sqle.getNextException();
if (nse != null && nse != sqle) {
checkException(nse);
}
@ -187,7 +197,7 @@ public abstract class ProxyConnection implements Connection
{
final int size = openStatements.size();
if (size > 0) {
for (int i = 0; i < size; i++) {
for (int i = 0; i < size && delegate != ClosedConnection.CLOSED_CONNECTION; i++) {
try {
final Statement statement = openStatements.get(i);
if (statement != null) {
@ -211,12 +221,14 @@ public abstract class ProxyConnection implements Connection
@Override
public final void close() throws SQLException
{
// Closing statements can cause connection eviction, so this must run before the conditional below
closeStatements();
if (delegate != ClosedConnection.CLOSED_CONNECTION) {
leakTask.cancel();
try {
closeStatements();
if (isCommitStateDirty && !isAutoCommit) {
if (isCommitStateDirty && !isAutoCommit && !isReadOnly) {
delegate.rollback();
lastAccess = clockSource.currentTime();
LOGGER.debug("{} - Executed rollback on connection {} due to dirty commit state on close().", poolEntry.getPoolName(), delegate);
@ -231,7 +243,7 @@ public abstract class ProxyConnection implements Connection
}
catch (SQLException e) {
// when connections are aborted, exceptions are often thrown that should not reach the application
if (!poolEntry.isEvicted()) {
if (!poolEntry.isMarkedEvicted()) {
throw checkException(e);
}
}
@ -366,7 +378,7 @@ public abstract class ProxyConnection implements Connection
{
delegate.setAutoCommit(autoCommit);
isAutoCommit = autoCommit;
dirtyBits |= 0b00010;
dirtyBits |= DIRTY_BIT_AUTOCOMMIT;
}
/** {@inheritDoc} */
@ -375,7 +387,7 @@ public abstract class ProxyConnection implements Connection
{
delegate.setReadOnly(readOnly);
isReadOnly = readOnly;
dirtyBits |= 0b00001;
dirtyBits |= DIRTY_BIT_READONLY;
}
/** {@inheritDoc} */
@ -384,7 +396,7 @@ public abstract class ProxyConnection implements Connection
{
delegate.setTransactionIsolation(level);
transactionIsolation = level;
dirtyBits |= 0b00100;
dirtyBits |= DIRTY_BIT_ISOLATION;
}
/** {@inheritDoc} */
@ -393,7 +405,7 @@ public abstract class ProxyConnection implements Connection
{
delegate.setCatalog(catalog);
dbcatalog = catalog;
dirtyBits |= 0b01000;
dirtyBits |= DIRTY_BIT_CATALOG;
}
/** {@inheritDoc} */
@ -402,7 +414,7 @@ public abstract class ProxyConnection implements Connection
{
delegate.setNetworkTimeout(executor, milliseconds);
networkTimeout = milliseconds;
dirtyBits |= 0b10000;
dirtyBits |= DIRTY_BIT_NETTIMEOUT;
}
/** {@inheritDoc} */

@ -39,15 +39,16 @@ public final class ProxyFactory
/**
* Create a proxy for the specified {@link Connection} instance.
* @param openStatements
* @param connection
*
* @param connectionState the PoolBagEntry entry for this proxy
* @param openStatements a leak detetection task
* @param now current timestamp in milliseconds
* @param poolEntry
* @param connection
* @param openStatements
* @param leakTask
* @param now
* @param isReadOnly
* @param isAutoCommit
* @return a proxy that wraps the specified {@link Connection}
*/
static ProxyConnection getProxyConnection(final PoolEntry poolEntry, final Connection connection, final FastList<Statement> openStatements, final ProxyLeakTask leakTask, final long now)
static ProxyConnection getProxyConnection(final PoolEntry poolEntry, final Connection connection, final FastList<Statement> openStatements, final ProxyLeakTask leakTask, final long now, final boolean isReadOnly, final boolean isAutoCommit)
{
// Body is replaced (injected) by JavassistProxyFactory
throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");

@ -42,6 +42,7 @@ import javassist.CtNewMethod;
import javassist.LoaderClassPath;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.ClassFile;
/**
* This class generates the proxy objects for {@link Connection}, {@link Statement},
@ -190,6 +191,7 @@ public final class JavassistProxyFactory
}
}
targetCt.getClassFile().setMajorVersion(ClassFile.JAVA_7);
targetCt.writeFile("target/classes");
}

@ -0,0 +1,83 @@
package com.zaxxer.hikari.pool;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.junit.Test;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
/**
* @author Matthew Tambara (matthew.tambara@liferay.com)
*/
public class AddConnectionRaceConditionTest
{
private HikariPool _hikariPool;
// @Test
public void testRaceCondition() throws Exception
{
HikariConfig config = new HikariConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(10);
config.setInitializationFailFast(false);
config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
// Initialize HikariPool with no initial connections and room to grow
try (final HikariDataSource ds = new HikariDataSource(config)) {
_hikariPool = TestElf.getPool(ds);
ExecutorService threadPool = Executors.newFixedThreadPool(2);
for (int i = 0; i < 100000; i++) {
Future<Exception> submit1 = threadPool.submit(new Callable<Exception>() {
/** {@inheritDoc} */
@Override
public Exception call() throws Exception
{
Connection c2;
try {
c2 = _hikariPool.getConnection(5000);
ds.evictConnection(c2);
}
catch (SQLException e) {
return e;
}
return null;
}
});
Future<Exception> submit2 = threadPool.submit(new Callable<Exception>() {
/** {@inheritDoc} */
@Override
public Exception call() throws Exception
{
Connection c2;
try {
c2 = _hikariPool.getConnection(5000);
ds.evictConnection(c2);
}
catch (SQLException e) {
return e;
}
return null;
}
});
if (submit1.get() != null) {
throw submit1.get();
}
if (submit2.get() != null) {
throw submit2.get();
}
}
}
catch (Exception e) {
throw e;
}
}
}

@ -1,5 +1,7 @@
package com.zaxxer.hikari.pool;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.Executors;
@ -9,6 +11,7 @@ import java.util.concurrent.TimeUnit;
import org.junit.Assert;
import org.junit.Test;
import com.sun.media.jfxmedia.logging.Logger;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.mocks.StubConnection;
@ -31,7 +34,7 @@ public class TestConnectionTimeoutRetry
try (HikariDataSource ds = new HikariDataSource(config)) {
StubDataSource stubDataSource = ds.unwrap(StubDataSource.class);
stubDataSource.setThrowException(new SQLException("Connection refused"));
long start = ClockSource.INSTANCE.currentTime();
try (Connection connection = ds.getConnection()) {
connection.close();
@ -60,7 +63,7 @@ public class TestConnectionTimeoutRetry
try (HikariDataSource ds = new HikariDataSource(config)) {
final StubDataSource stubDataSource = ds.unwrap(StubDataSource.class);
stubDataSource.setThrowException(new SQLException("Connection refused"));
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.schedule(new Runnable() {
public void run()
@ -68,12 +71,12 @@ public class TestConnectionTimeoutRetry
stubDataSource.setThrowException(null);
}
}, 300, TimeUnit.MILLISECONDS);
long start = ClockSource.INSTANCE.currentTime();
try {
Connection connection = ds.getConnection();
connection.close();
long elapsed = ClockSource.INSTANCE.elapsedMillis(start);
Assert.assertTrue("Connection returned too quickly, something is wrong.", elapsed > 250);
Assert.assertTrue("Waited too long to get a connection.", elapsed < config.getConnectionTimeout());
@ -103,7 +106,7 @@ public class TestConnectionTimeoutRetry
final Connection connection2 = ds.getConnection();
Assert.assertNotNull(connection1);
Assert.assertNotNull(connection2);
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
scheduler.schedule(new Runnable() {
public void run()
@ -116,12 +119,12 @@ public class TestConnectionTimeoutRetry
}
}
}, 800, TimeUnit.MILLISECONDS);
long start = ClockSource.INSTANCE.currentTime();
try {
Connection connection3 = ds.getConnection();
connection3.close();
long elapsed = ClockSource.INSTANCE.elapsedMillis(start);
Assert.assertTrue("Waited too long to get a connection.", (elapsed >= 700) && (elapsed < 950));
}
@ -148,7 +151,7 @@ public class TestConnectionTimeoutRetry
try (HikariDataSource ds = new HikariDataSource(config)) {
StubDataSource stubDataSource = ds.unwrap(StubDataSource.class);
stubDataSource.setThrowException(new SQLException("Connection refused"));
long start = ClockSource.INSTANCE.currentTime();
try {
Connection connection = ds.getConnection();
@ -175,9 +178,9 @@ public class TestConnectionTimeoutRetry
try (HikariDataSource ds = new HikariDataSource(config)) {
final Connection connection1 = ds.getConnection();
long start = ClockSource.INSTANCE.currentTime();
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
scheduler.schedule(new Runnable() {
public void run()
@ -190,14 +193,14 @@ public class TestConnectionTimeoutRetry
}
}
}, 250, TimeUnit.MILLISECONDS);
StubDataSource stubDataSource = ds.unwrap(StubDataSource.class);
stubDataSource.setThrowException(new SQLException("Connection refused"));
try {
Connection connection2 = ds.getConnection();
connection2.close();
long elapsed = ClockSource.INSTANCE.elapsedMillis(start);
Assert.assertTrue("Waited too long to get a connection.", (elapsed >= 250) && (elapsed < config.getConnectionTimeout()));
}
@ -223,8 +226,14 @@ public class TestConnectionTimeoutRetry
config.setConnectionTestQuery("VALUES 2");
config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "500");
System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "400");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos, true);
TestElf.setSlf4jTargetStream(HikariPool.class, ps);
TestElf.setSlf4jLogLevel(HikariPool.class, Logger.DEBUG);
boolean success = false;
try (HikariDataSource ds = new HikariDataSource(config)) {
Connection connection1 = ds.getConnection();
Connection connection2 = ds.getConnection();
@ -234,9 +243,9 @@ public class TestConnectionTimeoutRetry
Connection connection6 = ds.getConnection();
Connection connection7 = ds.getConnection();
Thread.sleep(1200);
Thread.sleep(900);
Assert.assertSame("Totals connections not as expected", 10, TestElf.getPool(ds).getTotalConnections());
Assert.assertSame("Total connections not as expected", 10, TestElf.getPool(ds).getTotalConnections());
Assert.assertSame("Idle connections not as expected", 3, TestElf.getPool(ds).getIdleConnections());
connection1.close();
@ -249,9 +258,14 @@ public class TestConnectionTimeoutRetry
Assert.assertSame("Totals connections not as expected", 10, TestElf.getPool(ds).getTotalConnections());
Assert.assertSame("Idle connections not as expected", 10, TestElf.getPool(ds).getIdleConnections());
success = true;
}
finally {
TestElf.setSlf4jLogLevel(HikariPool.class, Logger.INFO);
System.getProperties().remove("com.zaxxer.hikari.housekeeping.periodMs");
if (!success) {
System.err.println(new String(baos.toByteArray()));
}
}
}
}

@ -240,8 +240,6 @@ public class TestConnections
try {
Connection connection = ds.getConnection();
quietlySleep(1000);
Assert.assertEquals(1, TestElf.getPool(ds).getTotalConnections());
ds.evictConnection(connection);
Assert.assertEquals(0, TestElf.getPool(ds).getTotalConnections());
@ -264,7 +262,7 @@ public class TestConnections
HikariDataSource ds = new HikariDataSource(config);
try {
UtilityElf.quietlySleep(1200L);
UtilityElf.quietlySleep(500);
Assert.assertSame("Totals connections not as expected", 1, TestElf.getPool(ds).getTotalConnections());
Assert.assertSame("Idle connections not as expected", 1, TestElf.getPool(ds).getIdleConnections());
@ -273,8 +271,6 @@ public class TestConnections
Connection connection = ds.getConnection();
Assert.assertNotNull(connection);
quietlySleep(500);
Assert.assertSame("Totals connections not as expected", 1, TestElf.getPool(ds).getTotalConnections());
Assert.assertSame("Idle connections not as expected", 0, TestElf.getPool(ds).getIdleConnections());
@ -295,8 +291,6 @@ public class TestConnections
// The connection will be ejected from the pool here
connection.close();
quietlySleep(500);
Assert.assertSame("Totals connections not as expected", 0, TestElf.getPool(ds).getTotalConnections());
Assert.assertSame("Idle connections not as expected", 0, TestElf.getPool(ds).getIdleConnections());
@ -372,12 +366,12 @@ public class TestConnections
StubStatement.oldDriver = true;
HikariDataSource ds = new HikariDataSource(config);
try {
quietlySleep(1001);
quietlySleep(500);
Connection connection = ds.getConnection();
connection.close();
quietlySleep(1001);
quietlySleep(500);
connection = ds.getConnection();
}
finally {

Loading…
Cancel
Save