Fixes #307 implement a ClockSource that is backed by either System.currentTimeMillis() or System.nanoTime() depending on the platform.

pull/316/merge
Brett Wooldridge 10 years ago
commit c9041c83f2

@ -16,8 +16,6 @@
package com.zaxxer.hikari.metrics;
import static com.zaxxer.hikari.util.UtilityElf.elapsedTimeMs;
import java.util.concurrent.TimeUnit;
import com.codahale.metrics.CachedGauge;
@ -26,9 +24,12 @@ import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.zaxxer.hikari.pool.HikariPool;
import com.zaxxer.hikari.pool.PoolBagEntry;
import com.zaxxer.hikari.util.ClockSource;
public final class CodaHaleMetricsTracker extends MetricsTracker
{
private static final ClockSource clockSource = ClockSource.INSTANCE;
private final Timer connectionObtainTimer;
private final Histogram connectionUsage;
private final MetricRegistry registry;
@ -93,7 +94,7 @@ public final class CodaHaleMetricsTracker extends MetricsTracker
/** {@inheritDoc} */
@Override
public Context recordConnectionRequest(final long requestTime)
public Context recordConnectionRequest()
{
return new Context(connectionObtainTimer);
}
@ -102,7 +103,7 @@ public final class CodaHaleMetricsTracker extends MetricsTracker
@Override
public void recordConnectionUsage(final PoolBagEntry bagEntry)
{
connectionUsage.update(elapsedTimeMs(bagEntry.lastOpenTime));
connectionUsage.update(clockSource.elapsedTimeMs(bagEntry.lastOpenTime));
}
public Timer getConnectionAcquisitionTimer()
@ -132,9 +133,9 @@ public final class CodaHaleMetricsTracker extends MetricsTracker
/** {@inheritDoc} */
@Override
public void setConnectionLastOpen(final PoolBagEntry bagEntry, final long nowMillis)
public void setConnectionLastOpen(final PoolBagEntry bagEntry, final long now)
{
bagEntry.lastOpenTime = nowMillis;
bagEntry.lastOpenTime = now;
}
}
}

@ -35,7 +35,7 @@ public class MetricsTracker implements AutoCloseable
this.pool = pool;
}
public MetricsContext recordConnectionRequest(long requestTimeMillis)
public MetricsContext recordConnectionRequest()
{
return NO_CONTEXT;
}
@ -67,9 +67,9 @@ public class MetricsTracker implements AutoCloseable
* Set the lastOpenTime on the provided bag entry.
*
* @param bagEntry the bag entry
* @param nowMillis the last open timestamp from {@link System#currentTimeMillis()}
* @param now the last open timestamp from {@link ClockSource#currentTime()}
*/
public void setConnectionLastOpen(final PoolBagEntry bagEntry, final long nowMillis)
public void setConnectionLastOpen(final PoolBagEntry bagEntry, final long now)
{
// do nothing
}

@ -22,7 +22,6 @@ import static com.zaxxer.hikari.util.IConcurrentBagEntry.STATE_IN_USE;
import static com.zaxxer.hikari.util.IConcurrentBagEntry.STATE_NOT_IN_USE;
import static com.zaxxer.hikari.util.IConcurrentBagEntry.STATE_REMOVED;
import static com.zaxxer.hikari.util.UtilityElf.createThreadPoolExecutor;
import static com.zaxxer.hikari.util.UtilityElf.elapsedTimeMs;
import static com.zaxxer.hikari.util.UtilityElf.getTransactionIsolation;
import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
@ -55,6 +54,7 @@ import com.zaxxer.hikari.metrics.MetricsTracker.MetricsContext;
import com.zaxxer.hikari.proxy.ConnectionProxy;
import com.zaxxer.hikari.proxy.IHikariConnectionProxy;
import com.zaxxer.hikari.proxy.ProxyFactory;
import com.zaxxer.hikari.util.ClockSource;
import com.zaxxer.hikari.util.ConcurrentBag;
import com.zaxxer.hikari.util.DefaultThreadFactory;
import com.zaxxer.hikari.util.IBagStateListener;
@ -68,6 +68,9 @@ import com.zaxxer.hikari.util.IBagStateListener;
public class HikariPool implements HikariPoolMBean, IBagStateListener
{
protected final Logger LOGGER = LoggerFactory.getLogger(getClass());
private static final ClockSource clockSource = ClockSource.INSTANCE;
private final long ALIVE_BYPASS_WINDOW_MS = Long.getLong("com.zaxxer.hikari.aliveBypassWindow", TimeUnit.SECONDS.toMillis(1));
private final long HOUSEKEEPING_PERIOD_MS = Long.getLong("com.zaxxer.hikari.housekeeping.periodMs", TimeUnit.SECONDS.toMillis(30));
@ -176,8 +179,8 @@ public class HikariPool implements HikariPoolMBean, IBagStateListener
{
suspendResumeLock.acquire();
long timeout = hardTimeout;
final long start = System.currentTimeMillis();
final MetricsContext metricsContext = (isRecordMetrics ? metricsTracker.recordConnectionRequest(start) : MetricsTracker.NO_CONTEXT);
final long startTime = clockSource.currentTime();
final MetricsContext metricsContext = (isRecordMetrics ? metricsTracker.recordConnectionRequest() : MetricsTracker.NO_CONTEXT);
try {
do {
@ -186,10 +189,10 @@ public class HikariPool implements HikariPoolMBean, IBagStateListener
break; // We timed out... break and throw exception
}
final long now = System.currentTimeMillis();
if (bagEntry.evicted || (now - bagEntry.lastAccess > ALIVE_BYPASS_WINDOW_MS && !isConnectionAlive(bagEntry.connection))) {
final long now = clockSource.currentTime();
if (bagEntry.evicted || (clockSource.toMillis(now - bagEntry.lastAccess) > ALIVE_BYPASS_WINDOW_MS && !isConnectionAlive(bagEntry.connection))) {
closeConnection(bagEntry, "connection evicted or dead"); // Throw away the dead connection and try again
timeout = hardTimeout - elapsedTimeMs(start);
timeout = hardTimeout - clockSource.elapsedTimeMs(startTime);
}
else {
metricsContext.setConnectionLastOpen(bagEntry, now);
@ -207,7 +210,7 @@ public class HikariPool implements HikariPoolMBean, IBagStateListener
}
logPoolState("Timeout failure ");
throw new SQLTimeoutException(String.format("Timeout after %dms of waiting for a connection.", elapsedTimeMs(start)), lastConnectionFailure.getAndSet(null));
throw new SQLTimeoutException(String.format("Timeout after %dms of waiting for a connection.", clockSource.elapsedTimeMs(startTime), lastConnectionFailure.getAndSet(null)));
}
/**
@ -255,12 +258,12 @@ public class HikariPool implements HikariPoolMBean, IBagStateListener
final ExecutorService assassinExecutor = createThreadPoolExecutor(configuration.getMaximumPoolSize(), "HikariCP connection assassin",
configuration.getThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
try {
final long start = System.currentTimeMillis();
final long start = clockSource.currentTime();
do {
softEvictConnections();
abortActiveConnections(assassinExecutor);
}
while (getTotalConnections() > 0 && elapsedTimeMs(start) < TimeUnit.SECONDS.toMillis(5));
while (getTotalConnections() > 0 && clockSource.elapsedTimeMs(start) < TimeUnit.SECONDS.toMillis(5));
} finally {
assassinExecutor.shutdown();
assassinExecutor.awaitTermination(5L, TimeUnit.SECONDS);
@ -663,7 +666,7 @@ public class HikariPool implements HikariPoolMBean, IBagStateListener
if (bagEntry.evicted) {
closeConnection(bagEntry, "connection evicted");
}
else if (idleTimeout > 0L && now - bagEntry.lastAccess > idleTimeout) {
else if (idleTimeout > 0L && clockSource.elapsedTimeMs(bagEntry.lastAccess) > idleTimeout) {
closeConnection(bagEntry, "connection passed idleTimeout");
}
else {

@ -22,6 +22,7 @@ import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import com.zaxxer.hikari.util.ClockSource;
import com.zaxxer.hikari.util.FastList;
import com.zaxxer.hikari.util.IConcurrentBagEntry;
@ -48,7 +49,7 @@ public final class PoolBagEntry implements IConcurrentBagEntry
public PoolBagEntry(final Connection connection, final HikariPool pool) {
this.connection = connection;
this.parentPool = pool;
this.lastAccess = System.currentTimeMillis();
this.lastAccess = ClockSource.INSTANCE.currentTime();
this.openStatements = new FastList<Statement>(Statement.class, 16);
final long variance = pool.configuration.getMaxLifetime() > 60_000 ? ThreadLocalRandom.current().nextLong(10_000) : 0;

@ -31,6 +31,7 @@ import org.slf4j.LoggerFactory;
import com.zaxxer.hikari.pool.LeakTask;
import com.zaxxer.hikari.pool.PoolBagEntry;
import com.zaxxer.hikari.util.ClockSource;
import com.zaxxer.hikari.util.FastList;
/**
@ -42,6 +43,7 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy
{
private static final Logger LOGGER;
private static final Set<String> SQL_ERRORS;
private static final ClockSource clockSource = ClockSource.INSTANCE;
protected Connection delegate;
@ -165,7 +167,7 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy
delegate.setCatalog(bagEntry.parentPool.catalog);
}
lastAccess = System.currentTimeMillis();
lastAccess = clockSource.currentTime();
}
// **********************************************************************
@ -193,7 +195,7 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy
try {
if (isCommitStateDirty) {
lastAccess = System.currentTimeMillis();
lastAccess = clockSource.currentTime();
if (!delegate.getAutoCommit()) {
delegate.rollback();
@ -317,7 +319,7 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy
{
delegate.commit();
isCommitStateDirty = false;
lastAccess = System.currentTimeMillis();
lastAccess = clockSource.currentTime();
}
/** {@inheritDoc} */
@ -326,7 +328,7 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy
{
delegate.rollback();
isCommitStateDirty = false;
lastAccess = System.currentTimeMillis();
lastAccess = clockSource.currentTime();
}
/** {@inheritDoc} */
@ -335,7 +337,7 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy
{
delegate.rollback(savepoint);
isCommitStateDirty = false;
lastAccess = System.currentTimeMillis();
lastAccess = clockSource.currentTime();
}
/** {@inheritDoc} */

@ -0,0 +1,69 @@
/*
* Copyright (C) 2015 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.util;
/**
* A resolution-independent provider of current time-stamps and elapsed time
* calculations.
*
* @author Brett Wooldridge
*/
public interface ClockSource
{
final ClockSource INSTANCE = Factory.create();
/**
* Get the current time-stamp (resolution is opaque).
*
* @return the current time-stamp
*/
long currentTime();
/**
* Convert an opaque time-stamp returned by currentTime() into
* milliseconds.
*
* @param time an opaque time-stamp returned by an instance of this class
* @return the time-stamp in milliseconds
*/
long toMillis(long time);
/**
* Convert an opaque time-stamp returned by currentTime() into an
* elapsed time in milliseconds, based on the current instant in time.
*
* @param startTime an opaque time-stamp returned by an instance of this class
* @return the elapsed time between startTime and now in milliseconds
*/
long elapsedTimeMs(long startTime);
/**
* Factory class used to create a platform-specific ClockSource.
*/
class Factory
{
private static ClockSource create()
{
String os = System.getProperty("os.name");
if ("Mac OS X".equals(os)) {
return new MillisecondClockSource();
}
return new NanosecondClockSource();
}
}
}

@ -0,0 +1,48 @@
/*
* Copyright (C) 2013 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.util;
/**
* A System.currentTimeMillis()-based provider of current time-stamps and elapsed time
* calculations.
*
* @author Brett Wooldridge
*/
class MillisecondClockSource implements ClockSource
{
/** {@inheritDoc} */
@Override
public long currentTime()
{
return System.currentTimeMillis();
}
/** {@inheritDoc} */
@Override
public long elapsedTimeMs(final long startTime)
{
return System.currentTimeMillis() - startTime;
}
/** {@inheritDoc} */
@Override
public long toMillis(long time)
{
return time;
}
}

@ -0,0 +1,49 @@
/*
* Copyright (C) 2015 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.util;
import java.util.concurrent.TimeUnit;
/**
* A System.nanoTime()-based provider of current time-stamps and elapsed time
* calculations.
*
* @author Brett Wooldridge
*/
class NanosecondClockSource implements ClockSource
{
/** {@inheritDoc} */
@Override
public long currentTime()
{
return System.nanoTime();
}
/** {@inheritDoc} */
@Override
public long elapsedTimeMs(final long startTime)
{
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
}
/** {@inheritDoc} */
@Override
public final long toMillis(final long time)
{
return TimeUnit.NANOSECONDS.toMillis(time);
}
}

@ -31,28 +31,6 @@ import java.util.concurrent.TimeUnit;
*/
public final class UtilityElf
{
/**
* Get the elapsed time in nanoseconds between the specified start time and now.
*
* @param startNanos the start time
* @return the elapsed milliseconds
*/
public static long elapsedNanos(final long startNanos)
{
return System.nanoTime() - startNanos;
}
/**
* Get the elapsed time in millisecond between the specified start time and now.
*
* @param startMillis the start time
* @return the elapsed milliseconds
*/
public static long elapsedTimeMs(final long startMillis)
{
return System.currentTimeMillis() - startMillis;
}
/**
* Sleep and transform an InterruptedException into a RuntimeException.
*

@ -27,6 +27,7 @@ import java.util.concurrent.TimeUnit;
import org.junit.Assert;
import org.junit.Before;
import com.zaxxer.hikari.util.ClockSource;
import com.zaxxer.hikari.util.UtilityElf;
/**
@ -51,7 +52,7 @@ public class PostgresTest
config.setUsername("brettw");
try (final HikariDataSource ds = new HikariDataSource(config)) {
final long start = System.currentTimeMillis();
final long start = ClockSource.INSTANCE.currentTime();
do {
Thread t = new Thread() {
public void run() {
@ -68,7 +69,7 @@ public class PostgresTest
t.start();
UtilityElf.quietlySleep(TimeUnit.SECONDS.toMillis((long)((Math.random() * 20))));
} while (UtilityElf.elapsedTimeMs(start) < TimeUnit.MINUTES.toMillis(15));
} while (ClockSource.INSTANCE.elapsedTimeMs(start) < TimeUnit.MINUTES.toMillis(15));
}
}
@ -95,14 +96,14 @@ public class PostgresTest
System.err.println("\nNow attempting another getConnection(), expecting a timeout...");
long start = System.currentTimeMillis();
long start = ClockSource.INSTANCE.currentTime();
try (Connection conn = ds.getConnection()) {
System.err.println("\nOpps, got a connection. Did you enable the firewall? " + conn);
Assert.fail("Opps, got a connection. Did you enable the firewall?");
}
catch (SQLException e)
{
Assert.assertTrue("Timeout less than expected " + (System.currentTimeMillis() - start) + "ms", (System.currentTimeMillis() - start) > 5000);
Assert.assertTrue("Timeout less than expected " + ClockSource.INSTANCE.elapsedTimeMs(start) + "ms", ClockSource.INSTANCE.elapsedTimeMs(start) > 5000);
}
System.err.println("\nOk, so far so good. Now, disable the firewall again. Attempting connection in 5 seconds...");
@ -174,7 +175,7 @@ public class PostgresTest
threads.add(new Thread() {
public void run() {
UtilityElf.quietlySleep((long)(Math.random() * 2500L));
final long start = System.currentTimeMillis();
final long start = ClockSource.INSTANCE.currentTime();
do {
try (Connection conn = ds.getConnection(); Statement stmt = conn.createStatement()) {
try (ResultSet rs = stmt.executeQuery("SELECT * FROM device WHERE device_id=0 ORDER BY device_id LIMIT 1 OFFSET 0")) {
@ -188,7 +189,7 @@ public class PostgresTest
}
// UtilityElf.quietlySleep(10L); //Math.max(50L, (long)(Math.random() * 250L)));
} while (UtilityElf.elapsedTimeMs(start) < TimeUnit.MINUTES.toMillis(5));
} while (ClockSource.INSTANCE.elapsedTimeMs(start) < TimeUnit.MINUTES.toMillis(5));
};
});
}

@ -29,6 +29,7 @@ import org.junit.Test;
import com.zaxxer.hikari.mocks.StubConnection;
import com.zaxxer.hikari.pool.HikariPool;
import com.zaxxer.hikari.pool.PoolUtilities;
import com.zaxxer.hikari.util.ClockSource;
import com.zaxxer.hikari.util.UtilityElf;
/**
@ -170,8 +171,8 @@ public class ShutdownTest
ds.close();
long startNanos = System.nanoTime();
while (UtilityElf.elapsedNanos(startNanos) < TimeUnit.SECONDS.toMillis(5) && threadCount() > 0) {
long startTime = ClockSource.INSTANCE.currentTime();
while (ClockSource.INSTANCE.elapsedTimeMs(startTime) < TimeUnit.SECONDS.toMillis(5) && threadCount() > 0) {
UtilityElf.quietlySleep(250);
}

@ -17,6 +17,7 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import com.zaxxer.hikari.mocks.MockDataSource;
import com.zaxxer.hikari.util.ClockSource;
import com.zaxxer.hikari.util.UtilityElf;
/**
@ -38,20 +39,20 @@ public class TestConnectionCloseBlocking {
HikariDataSource ds = new HikariDataSource(config);
long start = System.currentTimeMillis();
long start = ClockSource.INSTANCE.currentTime();
try {
Connection connection = ds.getConnection();
connection.close();
// Hikari only checks for validity for connections with lastAccess > 1000 ms so we sleep for 1001 ms to force
// Hikari to do a connection validation which will fail and will trigger the connection to be closed
UtilityElf.quietlySleep(1001);
start = System.currentTimeMillis();
start = ClockSource.INSTANCE.currentTime();
connection = ds.getConnection(); // on physical connection close we sleep 2 seconds
Assert.assertTrue("Waited longer than timeout",
(UtilityElf.elapsedTimeMs(start) < config.getConnectionTimeout()));
(ClockSource.INSTANCE.elapsedTimeMs(start) < config.getConnectionTimeout()));
} catch (SQLException e) {
Assert.assertTrue("getConnection failed because close connection took longer than timeout",
(UtilityElf.elapsedNanos(start) < config.getConnectionTimeout()));
(ClockSource.INSTANCE.elapsedTimeMs(start) < config.getConnectionTimeout()));
} finally {
shouldSleep = false;
ds.close();

@ -11,6 +11,7 @@ import org.junit.Test;
import com.zaxxer.hikari.mocks.StubConnection;
import com.zaxxer.hikari.mocks.StubDataSource;
import com.zaxxer.hikari.util.ClockSource;
public class TestConnectionTimeoutRetry
{
@ -29,13 +30,13 @@ public class TestConnectionTimeoutRetry
StubDataSource stubDataSource = ds.unwrap(StubDataSource.class);
stubDataSource.setThrowException(new SQLException("Connection refused"));
long start = System.currentTimeMillis();
long start = ClockSource.INSTANCE.currentTime();
try (Connection connection = ds.getConnection()) {
connection.close();
Assert.fail("Should not have been able to get a connection.");
}
catch (SQLException e) {
long elapsed = System.currentTimeMillis() - start;
long elapsed = ClockSource.INSTANCE.elapsedTimeMs(start);
long timeout = config.getConnectionTimeout();
Assert.assertTrue("Didn't wait long enough for timeout", (elapsed >= timeout));
}
@ -66,12 +67,12 @@ public class TestConnectionTimeoutRetry
}
}, 300, TimeUnit.MILLISECONDS);
long start = System.currentTimeMillis();
long start = ClockSource.INSTANCE.currentTime();
try {
Connection connection = ds.getConnection();
connection.close();
long elapsed = System.currentTimeMillis() - start;
long elapsed = ClockSource.INSTANCE.elapsedTimeMs(start);
Assert.assertTrue("Connection returned too quickly, something is wrong.", elapsed > 250);
Assert.assertTrue("Waited too long to get a connection.", elapsed < config.getConnectionTimeout());
}
@ -114,12 +115,12 @@ public class TestConnectionTimeoutRetry
}
}, 800, TimeUnit.MILLISECONDS);
long start = System.currentTimeMillis();
long start = ClockSource.INSTANCE.currentTime();
try {
Connection connection3 = ds.getConnection();
connection3.close();
long elapsed = System.currentTimeMillis() - start;
long elapsed = ClockSource.INSTANCE.elapsedTimeMs(start);
Assert.assertTrue("Waited too long to get a connection.", (elapsed >= 700) && (elapsed < 950));
}
catch (SQLException e) {
@ -146,14 +147,14 @@ public class TestConnectionTimeoutRetry
StubDataSource stubDataSource = ds.unwrap(StubDataSource.class);
stubDataSource.setThrowException(new SQLException("Connection refused"));
long start = System.currentTimeMillis();
long start = ClockSource.INSTANCE.currentTime();
try {
Connection connection = ds.getConnection();
connection.close();
Assert.fail("Should not have been able to get a connection.");
}
catch (SQLException e) {
long elapsed = System.currentTimeMillis() - start;
long elapsed = ClockSource.INSTANCE.elapsedTimeMs(start);
Assert.assertTrue("Didn't wait long enough for timeout", (elapsed >= config.getConnectionTimeout()));
}
}
@ -173,7 +174,7 @@ public class TestConnectionTimeoutRetry
try (HikariDataSource ds = new HikariDataSource(config)) {
final Connection connection1 = ds.getConnection();
long start = System.currentTimeMillis();
long start = ClockSource.INSTANCE.currentTime();
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
scheduler.schedule(new Runnable() {
@ -195,7 +196,7 @@ public class TestConnectionTimeoutRetry
Connection connection2 = ds.getConnection();
connection2.close();
long elapsed = System.currentTimeMillis() - start;
long elapsed = ClockSource.INSTANCE.elapsedTimeMs(start);
Assert.assertTrue("Waited too long to get a connection.", (elapsed >= 250) && (elapsed < config.getConnectionTimeout()));
}
catch (SQLException e) {

Loading…
Cancel
Save