sequence
This article focuses on leakDetectionThreshold, also known as connection pool leak detection, for Hikari connection pools.
leakDetectionThreshold
This parameter is used to set the timeout period for a connection to be occupied, in milliseconds. The default value is 0, which disables connection leak detection.
If it is greater than 0 and not a unit test, then further judge: (leakDetectionThreshold < seconds.tomillis (2) or (leakDetectionThreshold > maxLifetime && maxLifetime > 0), will be reset to 0.
That is, to take effect, the value must be greater than 0 and cannot be shorter than 2 seconds. When maxLifetime is greater than 0, the value cannot be greater than maxLifetime. The default value is 1800000, that is, 30 minutes.
HikariPool.getConnection
HikariCP 2.7.6 – sources jar! /com/zaxxer/hikari/pool/HikariPool.java
/**
* Get a connection from the pool, or timeout after the specified number of milliseconds.
*
* @param hardTimeout the maximum time to wait for a connection from the pool
* @return a java.sql.Connection instance
* @throws SQLException thrown if a timeout occurs trying to obtain a connection
*/
public Connection getConnection(final long hardTimeout) throws SQLException
{
suspendResumeLock.acquire();
final long startTime = currentTime();
try {
long timeout = hardTimeout;
do {
PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
if (poolEntry == null) {
break; // We timed out... break and throw exception
}
final long now = currentTime();
if(poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > ALIVE_BYPASS_WINDOW_MS && ! isConnectionAlive(poolEntry.connection))) { closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE); timeout = hardTimeout - elapsedMillis(startTime); }else {
metricsTracker.recordBorrowStats(poolEntry, startTime);
returnpoolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now); }}while (timeout > 0L);
metricsTracker.recordBorrowTimeoutStats(startTime);
throw createTimeoutException(startTime);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new SQLException(poolName + " - Interrupted during connection acquisition", e);
}
finally {
suspendResumeLock.release(); }}Copy the code
Note that getConnection returns called when the poolEntry. CreateProxyConnection (leakTaskFactory. The schedule (poolEntry), now), ProxyLeakTask is associated with the proxy connection created here
leakTaskFactory
HikariCP 2.7.6 – sources jar! /com/zaxxer/hikari/pool/HikariPool.java
/**
* Construct a HikariPool with the specified configuration.
*
* @param config a HikariConfig instance
*/
public HikariPool(final HikariConfig config)
{
super(config);
this.connectionBag = new ConcurrentBag<>(this);
this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
this.houseKeepingExecutorService = initializeHouseKeepingExecutorService();
checkFailFast();
if(config.getMetricsTrackerFactory() ! = null) {setMetricsTrackerFactory(config.getMetricsTrackerFactory());
}
else {
setMetricRegistry(config.getMetricRegistry());
}
setHealthCheckRegistry(config.getHealthCheckRegistry());
registerMBeans(this);
ThreadFactory threadFactory = config.getThreadFactory();
LinkedBlockingQueue<Runnable> addConnectionQueue = new LinkedBlockingQueue<>(config.getMaximumPoolSize());
this.addConnectionQueue = unmodifiableCollection(addConnectionQueue);
this.addConnectionExecutor = createThreadPoolExecutor(addConnectionQueue, poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardPolicy());
this.closeConnectionExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), poolName + " connection closer", threadFactory, new ThreadPoolExecutor.CallerRunsPolicy()); this.leakTaskFactory = new ProxyLeakTaskFactory(config.getLeakDetectionThreshold(), houseKeepingExecutorService); this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, HOUSEKEEPING_PERIOD_MS, MILLISECONDS); } /** * Create/initialize the Housekeeping service {@link ScheduledExecutorService}. If the user specified an Executor * to be usedin the {@link HikariConfig}, then we use that. If no Executor was specified (typical), then create
* an Executor and configure it.
*
* @return either the user specified {@link ScheduledExecutorService}, or the one we created
*/
private ScheduledExecutorService initializeHouseKeepingExecutorService()
{
if (config.getScheduledExecutor() == null) {
final ThreadFactory threadFactory = Optional.ofNullable(config.getThreadFactory()).orElse(new DefaultThreadFactory(poolName + " housekeeper".true));
final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, threadFactory, new ThreadPoolExecutor.DiscardPolicy());
executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
executor.setRemoveOnCancelPolicy(true);
return executor;
}
else {
returnconfig.getScheduledExecutor(); }}Copy the code
Note the initialization leakTaskFactory, and houseKeepingExecutorService
ProxyLeakTaskFactory
HikariCP 2.7.6 – sources jar! /com/zaxxer/hikari/pool/ProxyLeakTaskFactory.java
/**
* A factory for {@link ProxyLeakTask} Runnables that are scheduled inthe future to report leaks. * * @author Brett Wooldridge * @author Andreas Brenk */ class ProxyLeakTaskFactory { private ScheduledExecutorService executorService; private long leakDetectionThreshold; ProxyLeakTaskFactory(final long leakDetectionThreshold, final ScheduledExecutorService executorService) { this.executorService = executorService; this.leakDetectionThreshold = leakDetectionThreshold; } ProxyLeakTask schedule(final PoolEntry poolEntry) {return (leakDetectionThreshold == 0) ? ProxyLeakTask.NO_LEAK : scheduleNewTask(poolEntry);
}
void updateLeakDetectionThreshold(final long leakDetectionThreshold)
{
this.leakDetectionThreshold = leakDetectionThreshold;
}
private ProxyLeakTask scheduleNewTask(PoolEntry poolEntry) {
ProxyLeakTask task = new ProxyLeakTask(poolEntry);
task.schedule(executorService, leakDetectionThreshold);
returntask; }}Copy the code
If leakDetectionThreshold=0 is disabled, schedule returns ProxyLeakTask.no_leak. Otherwise, create a new ProxyLeakTask. Triggered after leakDetectionThreshold
ProxyLeakTask
HikariCP 2.7.6 – sources jar! /com/zaxxer/hikari/pool/ProxyLeakTask.java
/**
* A Runnable that is scheduled in the future to report leaks. The ScheduledFuture is
* cancelled ifthe connection is closed before the leak time expires. * * @author Brett Wooldridge */ class ProxyLeakTask implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(ProxyLeakTask.class); static final ProxyLeakTask NO_LEAK; private ScheduledFuture<? > scheduledFuture; private String connectionName; private Exception exception; private String threadName; private boolean isLeaked; static { NO_LEAK = newProxyLeakTask() {
@Override
void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold) {}
@Override
public void run() {}
@Override
public void cancel() {}}; } ProxyLeakTask(final PoolEntry poolEntry) { this.exception = new Exception("Apparent connection leak detected");
this.threadName = Thread.currentThread().getName();
this.connectionName = poolEntry.connection.toString();
}
private ProxyLeakTask()
{
}
void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold)
{
scheduledFuture = executorService.schedule(this, leakDetectionThreshold, TimeUnit.MILLISECONDS);
}
/** {@inheritDoc} */
@Override
public void run()
{
isLeaked = true;
final StackTraceElement[] stackTrace = exception.getStackTrace();
final StackTraceElement[] trace = new StackTraceElement[stackTrace.length - 5];
System.arraycopy(stackTrace, 5, trace, 0, trace.length);
exception.setStackTrace(trace);
LOGGER.warn("Connection leak detection triggered for {} on thread {}, stack trace follows", connectionName, threadName, exception);
}
void cancel()
{
scheduledFuture.cancel(false);
if (isLeaked) {
LOGGER.info("Previously reported leaked connection {} on thread {} was returned to the pool (unleaked)", connectionName, threadName); }}}Copy the code
Note that the methods in NO_LEAK class are empty operations. Once the task is fired, throw exceptions (“Apparent Connection leak detected”)
ProxyConnection.close
HikariCP 2.7.6 – sources jar! /com/zaxxer/hikari/pool/ProxyConnection.java
/** {@inheritDoc} */
@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 {if(isCommitStateDirty && ! isAutoCommit) { delegate.rollback(); lastAccess = currentTime(); LOGGER.debug("{} - Executed rollback on connection {} due to dirty commit state on close().", poolEntry.getPoolName(), delegate);
}
if(dirtyBits ! = 0) { poolEntry.resetConnectionState(this, dirtyBits); lastAccess = currentTime(); } delegate.clearWarnings(); } catch (SQLException e) { // when connections are aborted, exceptions are often thrown that should not reach the applicationif(! poolEntry.isMarkedEvicted()) { throw checkException(e); } } finally { delegate = ClosedConnection.CLOSED_CONNECTION; poolEntry.recycle(lastAccess); } } } @SuppressWarnings("EmptyTryBlock")
private synchronized void closeStatements()
{
final int size = openStatements.size();
if (size > 0) {
for(int i = 0; i < size && delegate ! = ClosedConnection.CLOSED_CONNECTION; i++) { try (Statement ignored = openStatements.get(i)) { // automatic resource cleanup } catch (SQLException e) { LOGGER.warn("{} - Connection {} marked as broken because of an exception closing open statements during Connection.close()",
poolEntry.getPoolName(), delegate);
leakTask.cancel();
poolEntry.evict("(exception closing Statements during Connection.close())");
delegate = ClosedConnection.CLOSED_CONNECTION;
}
}
openStatements.clear();
}
}
final SQLException checkException(SQLException sqle)
{
SQLException nse = sqle;
for(int depth = 0; delegate ! = ClosedConnection.CLOSED_CONNECTION && nse ! = null && depth < 10; depth++) { final String sqlState = nse.getSQLState();if(sqlState ! = null && sqlState.startsWith("08") || ERROR_STATES.contains(sqlState) || ERROR_CODES.contains(nse.getErrorCode())) {
// broken connection
LOGGER.warn("{} - Connection {} marked as broken because of SQLSTATE({}), ErrorCode({})",
poolEntry.getPoolName(), delegate, sqlState, nse.getErrorCode(), nse);
leakTask.cancel();
poolEntry.evict("(connection is broken)");
delegate = ClosedConnection.CLOSED_CONNECTION;
}
else{ nse = nse.getNextException(); }}return sqle;
}
Copy the code
On close of connection, closeStatements, checkException will call leaktask.cancel (); Cancel the task that detects connection leaks. Plus in delegate! = closedConnection.closed_connection displays a call to leaktask.cancel ();
HikariPool.evictConnection
HikariCP 2.7.6 – sources jar! /com/zaxxer/hikari/pool/HikariPool.java
/**
* Evict a Connection from the pool.
*
* @param connection the Connection to evict (actually a {@link ProxyConnection})
*/
public void evictConnection(Connection connection)
{
ProxyConnection proxyConnection = (ProxyConnection) connection;
proxyConnection.cancelLeakTask();
try {
softEvictConnection(proxyConnection.getPoolEntry(), "(connection evicted by user)", !connection.isClosed() /* owner */);
}
catch (SQLException e) {
// unreachable in HikariCP, but we're still forced to catch it } }Copy the code
EvictConnection will also be cancelLeakTask if the user calls evictConnection manually
The instance
/**
* leak-detection-threshold: 5000
* @throws SQLException
* @throws InterruptedException
*/
@Test
public void testConnLeak() throws SQLException, InterruptedException {
Connection conn = dataSource.getConnection();
TimeUnit.SECONDS.sleep(10);
String sql = "select 1";
PreparedStatement pstmt = null;
try {
pstmt = (PreparedStatement)conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
int col = rs.getMetaData().getColumnCount();
while (rs.next()) {
for (int i = 1; i <= col; i++) {
System.out.print(rs.getObject(i));
}
System.out.println("");
}
System.out.println("= = = = = = = = = = = = = = = = = = = = = = = = = = = ="); } catch (Exception e) { e.printStackTrace(); } finally { //close resources DbUtils.closeQuietly(pstmt); DbUtils.closeQuietly(conn); }}Copy the code
The output
The 2018-01-29 22:46:31. 1454-028 WARN [l - 1 housekeeper] com. Zaxxer. Hikari. Pool. ProxyLeakTask: Connection leak detection triggeredfororg.postgresql.jdbc.PgConnection@3abd581e on thread main, stack trace follows java.lang.Exception: Apparent connection leak detected at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:123) ~ [HikariCP - 2.7.6 jar: na] at the example. The demo. HikariDemoApplicationTests. TestConnLeak (48) HikariDemoApplicationTests. Java: ~ [test- classes / : na] at sun reflect. NativeMethodAccessorImpl. Invoke0 (Native Method) ~ [na: 1.8.0 comes with _71] the at Sun. Reflect. NativeMethodAccessorImpl. Invoke (NativeMethodAccessorImpl. Java: 62) ~ [na: 1.8.0 comes with _71] the at Sun. Reflect. DelegatingMethodAccessorImpl. Invoke (43) DelegatingMethodAccessorImpl. Java: ~ [na: 1.8.0 comes with _71] the at Java. Lang. Reflect. Method. Invoke (497) Method. The Java: ~ [na: 1.8.0 comes with _71] at org. Junit. Runners. Model. FrameworkMethodThe $1RunReflectiveCall (FrameworkMethod. Java: 50) ~ [junit 4.12. Jar: 4.12] at Org. Junit. Internal. Runners. Model. ReflectiveCallable. Run (ReflectiveCallable. Java: 12) ~ [junit 4.12. Jar: 4.12] at Org. Junit. Runners. Model. FrameworkMethod. InvokeExplosively (FrameworkMethod. Java: 47) ~ [junit 4.12. Jar: 4.12] at Org. Junit. Internal. Runners. Statements. InvokeMethod. Evaluate (InvokeMethod. Java: 17) ~ [junit 4.12. Jar: 4.12] at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallba CKS. Java: 73) ~ [spring - test - 5.0.3. The jar: 5.0.3. RELEASE] the at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallback S.j ava: 83) ~ [spring - test - 5.0.3. The jar: 5.0.3. RELEASE] the at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.ja Va: (75) ~ [spring - test - 5.0.3. The jar: 5.0.3. RELEASE] the at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java : 86) ~ [spring - test - 5.0.3. The jar: 5.0.3. RELEASE] the at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) ~ [spring - test - 5.0.3. RELEASE. The jar: 5.0.3. RELEASE] at org. Junit. Runners. ParentRunner. RunLeaf (ParentRunner. Java: 325) ~ [junit 4.12. Jar: 4.12] at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251) ~ [spring - test - 5.0.3. RELEASE. The jar: 5.0.3. RELEASE] the at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) ~ [spring - test - 5.0.3. RELEASE. The jar: 5.0.3. RELEASE] at org. Junit. Runners. ParentRunner$3. The run (ParentRunner. Java: 290) ~ [junit 4.12. Jar: 4.12] at org. Junit. Runners. ParentRunnerThe $1. The schedule (ParentRunner. Java: 71) ~ [junit 4.12. Jar: 4.12] at Org. Junit. Runners. ParentRunner. RunChildren (ParentRunner. Java: 288) ~ [junit 4.12. Jar: 4.12] at org.junit.runners.ParentRunner.accessThe $000(ParentRunner. Java: 58) ~ [junit 4.12. Jar: 4.12] at org. Junit. Runners. ParentRunner$2. The evaluate (ParentRunner. Java: 268) ~ [junit 4.12. Jar: 4.12] at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java : 61) ~ [spring - test - 5.0.3. The jar: 5.0.3. RELEASE] the at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:7 0) ~ [spring - test - 5.0.3. The jar: 5.0.3. RELEASE] at org. Junit. Runners. ParentRunner. Run (ParentRunner. Java: 363) ~ [junit 4.12. Jar: 4.12] at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) ~ [spring - test - 5.0.3. RELEASE. The jar: 5.0.3. RELEASE] at org. Junit. Runner. JUnitCore. Run (JUnitCore. Java: 137) ~ [junit 4.12. Jar: 4.12] at the intellij. Takeup. JUnit4IdeaTestRunner. StartRunnerWithArgs (JUnit4IdeaTestRunner. Java: 69) ~[junit-rt.jar:na] at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234) ~[junit-rt.jar:na] at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74) ~[junit-rt.jar:na] at Sun. Reflect. NativeMethodAccessorImpl. Invoke0 (Native Method) ~ [na: 1.8.0 comes with _71] the at Sun. Reflect. NativeMethodAccessorImpl. Invoke (NativeMethodAccessorImpl. Java: 62) ~ [na: 1.8.0 comes with _71] the at Sun. Reflect. DelegatingMethodAccessorImpl. Invoke (43) DelegatingMethodAccessorImpl. Java: ~ [na: 1.8.0 comes with _71] the at Java. Lang. Reflect. Method. Invoke (497) Method. The Java: ~ [na: 1.8.0 comes with _71] the at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144) ~[idea_rt.jar:na] 1Copy the code
ProxyLeakTask throws java.lang.Exception: Apparent Connection leak detected, but this is thrown in Runnable and does not affect the main thread, which continues to execute after timeout and finally outputs the result.
summary
LeakDetectionThreshold of the hikari connection pool is used to set the timeout period for the connection to be occupied, in milliseconds. The default value is 0, which disables connection leak detection. This function is equivalent to the checkAbandoned in poolCleaner for Tomcat JDBC pool.
The differences are as follows:
- Tomcat JDBC pool is to use a timerTask, interval timeBetweenEvictionRunsMillis time allows a; Hikari creates a delayed timed task for each connection borrowed, and cancels the task in case of return or exception
- Tomcat JDBC pool is a direct abandon connection, that is, close, and then the connection will throw an exception in the subsequent data sending and receiving. Hikari takes the initiative to throw exceptions in ProxyLeakTask (“Apparent Connection leak detected”). Therefore, the former is violent, while the latter only throws an exception at Runnable and does not affect the subsequent operation of the connection.
doc
- configuration-knobs-baby