From f4e28160ce86fb4e9ddc417155da18cd038a0826 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Sat, 10 May 2014 21:54:38 +0900 Subject: [PATCH] Performance enhancements. --- CHANGES | 12 ++++ .../zaxxer/hikari/metrics/MetricsTracker.java | 3 +- .../com/zaxxer/hikari/pool/HikariPool.java | 41 +++++++------- .../zaxxer/hikari/proxy/ConnectionProxy.java | 20 ++++--- .../hikari/proxy/IHikariConnectionProxy.java | 9 +-- .../com/zaxxer/hikari/proxy/ProxyFactory.java | 2 +- .../com/zaxxer/hikari/TestConnections.java | 56 ++++++++++++++++++- 7 files changed, 104 insertions(+), 39 deletions(-) diff --git a/CHANGES b/CHANGES index 17ac6d8d..8dd27551 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,17 @@ HikariCP Changes +Changes between 1.3.8 and 1.3.9 + + *) Added pool name to housekeeping thread name to make thread dumps + more meaningful in containers with multiple pools. + + *) Performance enhancements. + +Changes between 1.3.7 and 1.3.8 + + *) Fixed incorrect logic when using JDBC4 isValid() test for alive + status of connection. + Changes between 1.3.6 and 1.3.7 *) Added JNDI object factory (com.zaxxer.hikari.HikariJNDIFactory) diff --git a/src/main/java/com/zaxxer/hikari/metrics/MetricsTracker.java b/src/main/java/com/zaxxer/hikari/metrics/MetricsTracker.java index 0b7068b7..f07ab03a 100644 --- a/src/main/java/com/zaxxer/hikari/metrics/MetricsTracker.java +++ b/src/main/java/com/zaxxer/hikari/metrics/MetricsTracker.java @@ -24,12 +24,13 @@ package com.zaxxer.hikari.metrics; */ public class MetricsTracker { - protected static final Context NO_CONTEXT = new Context(); + public static final Context NO_CONTEXT = new Context(); public static class Context { public void stop() { + // do nothing } } diff --git a/src/main/java/com/zaxxer/hikari/pool/HikariPool.java b/src/main/java/com/zaxxer/hikari/pool/HikariPool.java index fa5c80fc..8d3511b7 100644 --- a/src/main/java/com/zaxxer/hikari/pool/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/pool/HikariPool.java @@ -151,8 +151,8 @@ public final class HikariPool implements HikariPoolMBean, IBagStateListener public Connection getConnection() throws SQLException { final long start = System.currentTimeMillis(); - final long maxLife = configuration.getMaxLifetime(); - final Context context = metricsTracker.recordConnectionRequest(start); + final Context context = (isRecordMetrics ? metricsTracker.recordConnectionRequest(start) : MetricsTracker.NO_CONTEXT); + long timeout = configuration.getConnectionTimeout(); try { @@ -164,16 +164,16 @@ public final class HikariPool implements HikariPoolMBean, IBagStateListener break; } - connection.unclose(); - final long now = System.currentTimeMillis(); - if ((maxLife != 0 && now - connection.getCreationTime() > maxLife) || (now - connection.getLastAccess() > 1000 && !isConnectionAlive(connection, timeout))) + connection.unclose(now); + + if (now > connection.getExpirationTime() || (now - connection.getLastAccess() > 1000 && !isConnectionAlive(connection, timeout))) { closeConnection(connection); // Throw away the dead connection, try again timeout -= (System.currentTimeMillis() - start); continue; } - else if (leakDetectionThreshold > 0) + else if (leakDetectionThreshold != 0) { connection.captureStack(leakDetectionThreshold, houseKeepingTimer); } @@ -250,7 +250,6 @@ public final class HikariPool implements HikariPoolMBean, IBagStateListener return dataSource; } - /** * Permanently close a connection. * @@ -289,7 +288,8 @@ public final class HikariPool implements HikariPoolMBean, IBagStateListener public void run() { int sleepBackoff = 200; - while (totalConnections.get() < configuration.getMaximumPoolSize()) + final int maxPoolSize = configuration.getMaximumPoolSize(); + while (totalConnections.get() < maxPoolSize) { final int minIdle = configuration.getMinimumIdle(); if (minIdle != 0 && getIdleConnections() >= minIdle) @@ -395,7 +395,8 @@ public final class HikariPool implements HikariPoolMBean, IBagStateListener PoolUtilities.executeSqlAutoCommit(connection, configuration.getConnectionInitSql()); - IHikariConnectionProxy proxyConnection = ProxyFactory.getProxyConnection(this, connection, transactionIsolation, isAutoCommit, isReadOnly, catalog); + IHikariConnectionProxy proxyConnection = ProxyFactory.getProxyConnection(this, connection, configuration.getMaxLifetime(), + transactionIsolation, isAutoCommit, isReadOnly, catalog); proxyConnection.resetConnectionState(); connectionBag.add(proxyConnection); lastConnectionFailure.set(null); @@ -433,10 +434,9 @@ public final class HikariPool implements HikariPoolMBean, IBagStateListener { timeoutMs = Math.max(1000, timeoutMs); - boolean valid = true; if (isJdbc4ConnectionTest) { - valid = connection.isValid((int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs)); + return connection.isValid((int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs)); } else { @@ -453,14 +453,14 @@ public final class HikariPool implements HikariPoolMBean, IBagStateListener { statement.close(); } - } - if (isIsolateInternalQueries && !isAutoCommit) - { - connection.rollback(); + if (isIsolateInternalQueries && !isAutoCommit) + { + connection.rollback(); + } + + return true; } - - return valid; } catch (SQLException e) { @@ -535,8 +535,8 @@ public final class HikariPool implements HikariPoolMBean, IBagStateListener { int total = totalConnections.get(); int idle = getIdleConnections(); - LOGGER.debug("{}Pool stats {} (total={}, inUse={}, avail={}, waiting={})", (prefix.length > 0 ? prefix[0] : ""), configuration.getPoolName(), total, total - idle, idle, - getThreadsAwaitingConnection()); + LOGGER.debug("{}Pool stats {} (total={}, inUse={}, avail={}, waiting={})", (prefix.length > 0 ? prefix[0] : ""), + configuration.getPoolName(), total, total - idle, idle, getThreadsAwaitingConnection()); } /** @@ -554,7 +554,6 @@ public final class HikariPool implements HikariPoolMBean, IBagStateListener final long now = System.currentTimeMillis(); final long idleTimeout = configuration.getIdleTimeout(); - final long maxLifetime = configuration.getMaxLifetime(); for (IHikariConnectionProxy connectionProxy : connectionBag.values(ConcurrentBag.STATE_NOT_IN_USE)) { @@ -562,7 +561,7 @@ public final class HikariPool implements HikariPoolMBean, IBagStateListener { if ((idleTimeout > 0 && now > connectionProxy.getLastAccess() + idleTimeout) || - (maxLifetime > 0 && now > connectionProxy.getCreationTime() + maxLifetime)) + (now > connectionProxy.getExpirationTime())) { closeConnection(connectionProxy); continue; diff --git a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java index b65899cd..c067d716 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java @@ -46,14 +46,14 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy protected final Connection delegate; - private final long creationTime; private final FastStatementList openStatements; private final HikariPool parentPool; + private final AtomicInteger state; + private final String defaultCatalog; + private final long expirationTime; private final int defaultIsolationLevel; private final boolean defaultAutoCommit; private final boolean defaultReadOnly; - private final String defaultCatalog; - private final AtomicInteger state; private boolean forceClose; private boolean isAutoCommitDirty; @@ -81,7 +81,7 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy SQL_ERRORS.add("JZ0C1"); // Sybase disconnect error } - protected ConnectionProxy(HikariPool pool, Connection connection, int defaultIsolationLevel, boolean defaultAutoCommit, boolean defaultReadOnly, String defaultCatalog) + protected ConnectionProxy(HikariPool pool, Connection connection, long maxLifetime, int defaultIsolationLevel, boolean defaultAutoCommit, boolean defaultReadOnly, String defaultCatalog) { this.parentPool = pool; this.delegate = connection; @@ -91,7 +91,9 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy this.defaultCatalog = defaultCatalog; this.state = new AtomicInteger(); - this.creationTime = lastAccess = System.currentTimeMillis(); + long now = System.currentTimeMillis(); + this.expirationTime = (maxLifetime > 0 ? now + maxLifetime : Long.MAX_VALUE); + this.lastAccess = now; this.openStatements = new FastStatementList(); this.hashCode = System.identityHashCode(this); @@ -152,9 +154,9 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy /** {@inheritDoc} */ @Override - public final long getCreationTime() + public final long getExpirationTime() { - return creationTime; + return expirationTime; } /** {@inheritDoc} */ @@ -223,10 +225,10 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy /** {@inheritDoc} */ @Override - public final void unclose() + public final void unclose(final long now) { isClosed = false; - uncloseTime = System.currentTimeMillis(); + uncloseTime = now; } /** {@inheritDoc} */ diff --git a/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java index 5504cb22..17f31cc4 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java @@ -47,11 +47,11 @@ public interface IHikariConnectionProxy extends Connection, IBagManagable void checkException(SQLException sqle); /** - * Get the creation timestamp of the connection. + * Get the expiration timestamp of the connection. * - * @return the creation timestamp + * @return the expiration timestamp, or Long.MAX_VALUE if there is no maximum lifetime */ - long getCreationTime(); + long getExpirationTime(); /** * Get the last access timestamp of the connection. @@ -91,8 +91,9 @@ public interface IHikariConnectionProxy extends Connection, IBagManagable /** * Make the Connection available for use again by marking it as not closed. + * @param now the current time in milliseconds */ - void unclose(); + void unclose(long now); /** * Called by Statement and its subclasses when they are closed to remove them diff --git a/src/main/java/com/zaxxer/hikari/proxy/ProxyFactory.java b/src/main/java/com/zaxxer/hikari/proxy/ProxyFactory.java index c7b0fdee..0ac16edd 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/ProxyFactory.java +++ b/src/main/java/com/zaxxer/hikari/proxy/ProxyFactory.java @@ -47,7 +47,7 @@ public final class ProxyFactory * @param defaultCatalog the default catalog of the underlying {@link Connection} * @return a proxy that wraps the specified {@link Connection} */ - public static IHikariConnectionProxy getProxyConnection(HikariPool pool, Connection connection, int defaultIsolationLevel, boolean defaultAutoCommit, boolean defaultIReadOnly, String defaultCatalog) + public static IHikariConnectionProxy getProxyConnection(HikariPool pool, Connection connection, long maxLifeTime, int defaultIsolationLevel, boolean defaultAutoCommit, boolean defaultIReadOnly, String defaultCatalog) { // Body is injected by JavassistProxyFactory return null; diff --git a/src/test/java/com/zaxxer/hikari/TestConnections.java b/src/test/java/com/zaxxer/hikari/TestConnections.java index 457c97c4..6ade622d 100644 --- a/src/test/java/com/zaxxer/hikari/TestConnections.java +++ b/src/test/java/com/zaxxer/hikari/TestConnections.java @@ -86,7 +86,7 @@ public class TestConnections public void testMaxLifetime() throws Exception { HikariConfig config = new HikariConfig(); - config.setMinimumIdle(1); + config.setMinimumIdle(0); config.setMaximumPoolSize(1); config.setInitializationFailFast(true); config.setConnectionTestQuery("VALUES 1"); @@ -102,8 +102,8 @@ public class TestConnections ds.setMaxLifetime(700); - Assert.assertSame("Total connections not as expected", 1, TestElf.getPool(ds).getTotalConnections()); - Assert.assertSame("Idle connections not as expected", 1, TestElf.getPool(ds).getIdleConnections()); + Assert.assertSame("Total connections not as expected", 0, TestElf.getPool(ds).getTotalConnections()); + Assert.assertSame("Idle connections not as expected", 0, TestElf.getPool(ds).getIdleConnections()); Connection connection = ds.getConnection(); Assert.assertNotNull(connection); @@ -136,6 +136,56 @@ public class TestConnections } } + @Test + public void testMaxLifetime2() throws Exception + { + HikariConfig config = new HikariConfig(); + config.setMinimumIdle(0); + config.setMaximumPoolSize(1); + config.setInitializationFailFast(true); + config.setConnectionTestQuery("VALUES 1"); + config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); + + HikariDataSource ds = new HikariDataSource(config); + + try + { + ds.setMaxLifetime(700); + + Assert.assertSame("Total connections not as expected", 0, TestElf.getPool(ds).getTotalConnections()); + Assert.assertSame("Idle connections not as expected", 0, TestElf.getPool(ds).getIdleConnections()); + + Connection connection = ds.getConnection(); + Assert.assertNotNull(connection); + + Assert.assertSame("Second total connections not as expected", 1, TestElf.getPool(ds).getTotalConnections()); + Assert.assertSame("Second idle connections not as expected", 0, TestElf.getPool(ds).getIdleConnections()); + connection.close(); + + Assert.assertSame("Idle connections not as expected", 1, TestElf.getPool(ds).getIdleConnections()); + + Connection connection2 = ds.getConnection(); + Assert.assertSame("Expected the same connection", connection, connection2); + Assert.assertSame("Second total connections not as expected", 1, TestElf.getPool(ds).getTotalConnections()); + Assert.assertSame("Second idle connections not as expected", 0, TestElf.getPool(ds).getIdleConnections()); + connection2.close(); + + Thread.sleep(800); + + connection2 = ds.getConnection(); + Assert.assertNotSame("Expected a different connection", connection, connection2); + + connection2.close(); + + Assert.assertSame("Post total connections not as expected", 1, TestElf.getPool(ds).getTotalConnections()); + Assert.assertSame("Post idle connections not as expected", 1, TestElf.getPool(ds).getIdleConnections()); + } + finally + { + ds.shutdown(); + } + } + @Test public void testDoubleClose() throws Exception {