Possibly Fixes #606 Fixes ##610 housekeeper was running before all class members were initialised leading to an NPE in the housekeeper. Whether or not a particular VM would recover depends somewhat on its memory model. The pool on OS X did recover after the initial failure.

pull/620/head
Brett Wooldridge 9 years ago
parent 73777265b1
commit 8dde168adb

@ -187,6 +187,12 @@
<version>${pax.url.version}</version> <version>${pax.url.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.191</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

@ -53,6 +53,7 @@ public class HikariConfig implements HikariConfigMXBean
private static final long VALIDATION_TIMEOUT = SECONDS.toMillis(5); private static final long VALIDATION_TIMEOUT = SECONDS.toMillis(5);
private static final long IDLE_TIMEOUT = MINUTES.toMillis(10); private static final long IDLE_TIMEOUT = MINUTES.toMillis(10);
private static final long MAX_LIFETIME = MINUTES.toMillis(30); private static final long MAX_LIFETIME = MINUTES.toMillis(30);
private static final int DEFAULT_POOL_SIZE = 10;
private static boolean unitTest; private static boolean unitTest;
@ -825,7 +826,7 @@ public class HikariConfig implements HikariConfigMXBean
} }
if (maxPoolSize < 1) { if (maxPoolSize < 1) {
maxPoolSize = (minIdle <= 0) ? 10 : minIdle; maxPoolSize = (minIdle <= 0) ? DEFAULT_POOL_SIZE : minIdle;
} }
if (minIdle < 0 || minIdle > maxPoolSize) { if (minIdle < 0 || minIdle > maxPoolSize) {

@ -131,9 +131,9 @@ public class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateL
this.houseKeepingExecutorService = config.getScheduledExecutorService(); this.houseKeepingExecutorService = config.getScheduledExecutorService();
} }
this.houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 0L, HOUSEKEEPING_PERIOD_MS, MILLISECONDS);
this.leakTask = new ProxyLeakTask(config.getLeakDetectionThreshold(), houseKeepingExecutorService); this.leakTask = new ProxyLeakTask(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
this.houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, HOUSEKEEPING_PERIOD_MS, MILLISECONDS);
} }
/** /**
@ -587,54 +587,59 @@ public class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateL
@Override @Override
public void run() public void run()
{ {
// refresh timeouts in case they changed via MBean try {
connectionTimeout = config.getConnectionTimeout(); // refresh timeouts in case they changed via MBean
validationTimeout = config.getValidationTimeout(); connectionTimeout = config.getConnectionTimeout();
leakTask.updateLeakDetectionThreshold(config.getLeakDetectionThreshold()); validationTimeout = config.getValidationTimeout();
leakTask.updateLeakDetectionThreshold(config.getLeakDetectionThreshold());
final long idleTimeout = config.getIdleTimeout();
final long now = clockSource.currentTime(); final long idleTimeout = config.getIdleTimeout();
final long now = clockSource.currentTime();
// Detect retrograde time, allowing +128ms as per NTP spec.
if (clockSource.plusMillis(now, 128) < clockSource.plusMillis(previous, HOUSEKEEPING_PERIOD_MS)) { // Detect retrograde time, allowing +128ms as per NTP spec.
LOGGER.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.", if (clockSource.plusMillis(now, 128) < clockSource.plusMillis(previous, HOUSEKEEPING_PERIOD_MS)) {
clockSource.elapsedDisplayString(previous, now), poolName); LOGGER.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.",
clockSource.elapsedDisplayString(previous, now), poolName);
previous = now;
softEvictConnections();
fillPool();
return;
}
else if (now > clockSource.plusMillis(previous, (3 * HOUSEKEEPING_PERIOD_MS) / 2)) {
// No point evicting for forward clock motion, this merely accelerates connection retirement anyway
LOGGER.warn("{} - Thread starvation or clock leap detected (housekeeper delta={}).", clockSource.elapsedDisplayString(previous, now), poolName);
}
previous = now; previous = now;
softEvictConnections();
fillPool(); String afterPrefix = "Pool ";
return; if (idleTimeout > 0L) {
} final List<PoolEntry> idleList = connectionBag.values(STATE_NOT_IN_USE);
else if (now > clockSource.plusMillis(previous, (3 * HOUSEKEEPING_PERIOD_MS) / 2)) { int removable = idleList.size() - config.getMinimumIdle();
// No point evicting for forward clock motion, this merely accelerates connection retirement anyway if (removable > 0) {
LOGGER.warn("{} - Thread starvation or clock leap detected (housekeeper delta={}).", clockSource.elapsedDisplayString(previous, now), poolName); logPoolState("Before cleanup ");
} afterPrefix = "After cleanup ";
previous = now; // Sort pool entries on lastAccessed
Collections.sort(idleList, LASTACCESS_COMPARABLE);
String afterPrefix = "Pool "; for (PoolEntry poolEntry : idleList) {
if (idleTimeout > 0L) { if (clockSource.elapsedMillis(poolEntry.lastAccessed, now) > idleTimeout && connectionBag.reserve(poolEntry)) {
final List<PoolEntry> idleList = connectionBag.values(STATE_NOT_IN_USE); closeConnection(poolEntry, "(connection has passed idleTimeout)");
int removable = idleList.size() - config.getMinimumIdle(); if (--removable == 0) {
if (removable > 0) { break; // keep min idle cons
logPoolState("Before cleanup "); }
afterPrefix = "After cleanup ";
// Sort pool entries on lastAccessed
Collections.sort(idleList, LASTACCESS_COMPARABLE);
for (PoolEntry poolEntry : idleList) {
if (clockSource.elapsedMillis(poolEntry.lastAccessed, now) > idleTimeout && connectionBag.reserve(poolEntry)) {
closeConnection(poolEntry, "(connection has passed idleTimeout)");
if (--removable == 0) {
break; // keep min idle cons
} }
} }
} }
} }
logPoolState(afterPrefix);
fillPool(); // Try to maintain minimum connections
}
catch (Exception e) {
LOGGER.error("Unexpected exception in housekeeping task", e);
} }
logPoolState(afterPrefix);
fillPool(); // Try to maintain minimum connections
} }
} }

@ -12,6 +12,7 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.SQLTransientConnectionException;
import java.sql.Statement; import java.sql.Statement;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -313,6 +314,10 @@ abstract class PoolBase
String password = config.getPassword(); String password = config.getPassword();
connection = (username == null) ? dataSource.getConnection() : dataSource.getConnection(username, password); connection = (username == null) ? dataSource.getConnection() : dataSource.getConnection(username, password);
if (connection == null) {
throw new SQLTransientConnectionException("DataSource returned null unexpectedly");
}
setupConnection(connection); setupConnection(connection);
lastConnectionFailure.set(null); lastConnectionFailure.set(null);
return connection; return connection;

@ -0,0 +1,144 @@
/*
* Copyright (C) 2016 Brett Wooldridge
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zaxxer.hikari.db;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.TimeUnit;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.pool.HikariPool;
import com.zaxxer.hikari.pool.TestElf;
/**
* @author brettw
*
*/
public class BasicPoolTest
{
@Before
public void setup() throws SQLException
{
HikariConfig config = new HikariConfig();
config.setMinimumIdle(1);
config.setMaximumPoolSize(2);
config.setConnectionTestQuery("SELECT 1");
config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource");
config.addDataSourceProperty("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
try (HikariDataSource ds = new HikariDataSource(config);
Connection conn = ds.getConnection();
Statement stmt = conn.createStatement()) {
stmt.executeUpdate("DROP TABLE IF EXISTS basic_pool_test");
stmt.executeUpdate("CREATE TABLE basic_pool_test ("
+ "id INTEGER NOT NULL IDENTITY PRIMARY KEY, "
+ "timestamp TIMESTAMP, "
+ "string VARCHAR(128), "
+ "string_from_number NUMERIC "
+ ")");
}
}
@Test
public void testIdleTimeout() throws InterruptedException, SQLException
{
HikariConfig config = new HikariConfig();
config.setMinimumIdle(5);
config.setMaximumPoolSize(10);
config.setConnectionTestQuery("SELECT 1");
config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource");
config.addDataSourceProperty("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "1000");
try (HikariDataSource ds = new HikariDataSource(config)) {
System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs");
TimeUnit.SECONDS.sleep(1);
HikariPool pool = TestElf.getPool(ds);
ds.setIdleTimeout(3000);
Assert.assertSame("Total connections not as expected", 5, pool.getTotalConnections());
Assert.assertSame("Idle connections not as expected", 5, pool.getIdleConnections());
Connection connection = ds.getConnection();
Assert.assertNotNull(connection);
TimeUnit.MILLISECONDS.sleep(1500);
Assert.assertSame("Second total connections not as expected", 6, pool.getTotalConnections());
Assert.assertSame("Second idle connections not as expected", 5, pool.getIdleConnections());
connection.close();
Assert.assertSame("Idle connections not as expected", 6, pool.getIdleConnections());
TimeUnit.SECONDS.sleep(2);
Assert.assertSame("Third total connections not as expected", 5, pool.getTotalConnections());
Assert.assertSame("Third idle connections not as expected", 5, pool.getIdleConnections());
}
}
@Test
public void testIdleTimeout2() throws InterruptedException, SQLException
{
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(50);
config.setConnectionTestQuery("SELECT 1");
config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource");
config.addDataSourceProperty("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "1000");
try (HikariDataSource ds = new HikariDataSource(config)) {
System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs");
TimeUnit.SECONDS.sleep(1);
HikariPool pool = TestElf.getPool(ds);
ds.setIdleTimeout(3000);
Assert.assertSame("Total connections not as expected", 50, pool.getTotalConnections());
Assert.assertSame("Idle connections not as expected", 50, pool.getIdleConnections());
Connection connection = ds.getConnection();
Assert.assertNotNull(connection);
TimeUnit.MILLISECONDS.sleep(1500);
Assert.assertSame("Second total connections not as expected", 50, pool.getTotalConnections());
Assert.assertSame("Second idle connections not as expected", 49, pool.getIdleConnections());
connection.close();
Assert.assertSame("Idle connections not as expected", 50, pool.getIdleConnections());
TimeUnit.SECONDS.sleep(3);
Assert.assertSame("Third total connections not as expected", 50, pool.getTotalConnections());
Assert.assertSame("Third idle connections not as expected", 50, pool.getIdleConnections());
}
}
}

@ -508,4 +508,48 @@ public class TestConnections
Assert.assertSame("Bad query or something.", e.getNextException().getMessage()); Assert.assertSame("Bad query or something.", e.getNextException().getMessage());
} }
} }
@Test
public void testPopulationSlowAcquisition() throws InterruptedException, SQLException
{
HikariConfig config = new HikariConfig();
config.setPoolName("SlowAcquisition");
config.setMaximumPoolSize(30);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "1000");
StubConnection.slowCreate = true;
try (HikariDataSource ds = new HikariDataSource(config)) {
System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs");
ds.setIdleTimeout(3000);
TimeUnit.SECONDS.sleep(2);
HikariPool pool = TestElf.getPool(ds);
Assert.assertSame("Total connections not as expected", 1, pool.getTotalConnections());
Assert.assertSame("Idle connections not as expected", 1, pool.getIdleConnections());
Connection connection = ds.getConnection();
Assert.assertNotNull(connection);
TimeUnit.SECONDS.sleep(30);
Assert.assertSame("Second total connections not as expected", 30, pool.getTotalConnections());
Assert.assertSame("Second idle connections not as expected", 29, pool.getIdleConnections());
connection.close();
Assert.assertSame("Idle connections not as expected", 30, pool.getIdleConnections());
TimeUnit.SECONDS.sleep(5);
Assert.assertSame("Third total connections not as expected", 30, pool.getTotalConnections());
Assert.assertSame("Third idle connections not as expected", 30, pool.getIdleConnections());
}
finally {
StubConnection.slowCreate = false;
}
}
} }

Loading…
Cancel
Save