From a2eee447b20ab36b9c7d50c3266b824d35650232 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Tue, 22 Oct 2013 22:43:29 +0900 Subject: [PATCH] More performance tweaks. --- .../java/com/zaxxer/hikari/HikariPool.java | 102 ++++++++++++---- .../javassist/HikariClassTransformer.java | 115 ++++++------------ .../zaxxer/hikari/proxy/ConnectionProxy.java | 35 ++++-- .../zaxxer/hikari/proxy/HikariProxyBase.java | 3 - .../java/com/zaxxer/hikari/CreationTest.java | 3 + .../zaxxer/hikari/performance/Benchmark2.java | 2 +- 6 files changed, 147 insertions(+), 113 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/HikariPool.java b/src/main/java/com/zaxxer/hikari/HikariPool.java index a275f7e3..415eb551 100644 --- a/src/main/java/com/zaxxer/hikari/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/HikariPool.java @@ -20,11 +20,8 @@ import java.lang.management.ManagementFactory; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; -import java.util.Collections; -import java.util.Set; import java.util.Timer; import java.util.TimerTask; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -42,19 +39,25 @@ import com.zaxxer.hikari.proxy.JavassistProxyFactoryFactory; import com.zaxxer.hikari.util.ClassLoaderUtils; import com.zaxxer.hikari.util.PropertyBeanSetter; +/** + * This is the primary connection pool class that provides the basic + * pooling behavior for HikariCP. + * + * @author Brett Wooldridge + */ public class HikariPool implements HikariPoolMBean { private static final Logger LOGGER = LoggerFactory.getLogger(HikariPool.class); private final HikariConfig configuration; private final LinkedTransferQueue idleConnections; - private final Set inUseConnections; private final AtomicInteger totalConnections; private final AtomicInteger idleConnectionCount; private final DataSource dataSource; + private final long leakDetectionThreshold; private final boolean jdbc4ConnectionTest; - private volatile boolean delegationProxies; + private final boolean delegationProxies; private final Timer houseKeepingTimer; @@ -71,9 +74,9 @@ public class HikariPool implements HikariPoolMBean this.totalConnections = new AtomicInteger(); this.idleConnectionCount = new AtomicInteger(); this.idleConnections = new LinkedTransferQueue(); - this.inUseConnections = Collections.newSetFromMap(new ConcurrentHashMap(configuration.getMaximumPoolSize() * 2, 0.75f, 100)); this.jdbc4ConnectionTest = configuration.isJdbc4ConnectionTest(); + this.leakDetectionThreshold = configuration.getLeakDetectionThreshold(); try { @@ -82,9 +85,9 @@ public class HikariPool implements HikariPoolMBean PropertyBeanSetter.setTargetFromProperties(dataSource, configuration.getDataSourceProperties()); HikariInstrumentationAgent instrumentationAgent = new HikariInstrumentationAgent(dataSource); - if (false || !instrumentationAgent.loadTransformerAgent()) + delegationProxies = !instrumentationAgent.loadTransformerAgent(); + if (delegationProxies) { - delegationProxies = true; LOGGER.info("Falling back to Javassist delegate-based proxies."); } } @@ -106,6 +109,12 @@ public class HikariPool implements HikariPoolMBean fillPool(); } + /** + * Get a connection from the pool, or timeout trying. + * + * @return a java.sql.Connection instance + * @throws SQLException thrown if a timeout occurs trying to obtain a connection + */ Connection getConnection() throws SQLException { try @@ -131,32 +140,33 @@ public class HikariPool implements HikariPoolMBean final long maxLifetime = configuration.getMaxLifetime(); if (maxLifetime > 0 && start - connectionProxy.getCreationTime() > maxLifetime) { - // Throw away the connection that has passed its lifetime + // Throw away the connection that has passed its lifetime, try again closeConnection(connectionProxy); timeout -= (System.currentTimeMillis() - start); continue; } + connectionProxy.unclose(); + Connection connection = (Connection) connectionProxy; if (!isConnectionAlive(connection, timeout)) { - // Throw away the dead connection + // Throw away the dead connection, try again closeConnection(connectionProxy); timeout -= (System.currentTimeMillis() - start); continue; } - if (configuration.getLeakDetectionThreshold() > 0) + if (leakDetectionThreshold > 0) { - connectionProxy.captureStack(configuration.getLeakDetectionThreshold(), houseKeepingTimer); + connectionProxy.captureStack(leakDetectionThreshold, houseKeepingTimer); } - connectionProxy.unclose(); - inUseConnections.add(connectionProxy); - return connection; - } while (true); + } while (timeout > 0); + + throw new SQLException("Timeout of encountered waiting for connection"); } catch (InterruptedException e) { @@ -164,14 +174,14 @@ public class HikariPool implements HikariPoolMBean } } + /** + * Release a connection back to the pool, or permanently close it if it + * is broken. + * + * @param connectionProxy the connection to release back to the pool + */ public void releaseConnection(IHikariConnectionProxy connectionProxy) { - boolean existing = inUseConnections.remove(connectionProxy); - if (!existing) - { - LOGGER.warn("Internal pool state inconsistency", new Throwable()); - } - if (!connectionProxy.isBrokenConnection()) { connectionProxy.setLastAccess(System.currentTimeMillis()); @@ -184,6 +194,10 @@ public class HikariPool implements HikariPoolMBean } } + // *********************************************************************** + // HikariPoolMBean methods + // *********************************************************************** + /** {@inheritDoc} */ public int getActiveConnections() { @@ -226,6 +240,13 @@ public class HikariPool implements HikariPoolMBean } } + // *********************************************************************** + // Private methods + // *********************************************************************** + + /** + * Fill the pool up to the minimum size. + */ private void fillPool() { int maxIters = (configuration.getMinimumPoolSize() / configuration.getAcquireIncrement()) + 1; @@ -235,16 +256,22 @@ public class HikariPool implements HikariPoolMBean } } + /** + * Add connections to the pool, not exceeding the maximum allowed. + */ private synchronized void addConnections() { final int max = configuration.getMaximumPoolSize(); final int increment = configuration.getAcquireIncrement(); - for (int i = 0; i < increment && totalConnections.get() < max; i++) + for (int i = 0; totalConnections.get() < max && i < increment; i++) { addConnection(); } } + /** + * Create and add a single connection to the pool. + */ private void addConnection() { int retries = 0; @@ -298,8 +325,21 @@ public class HikariPool implements HikariPoolMBean } } - private boolean isConnectionAlive(Connection connection, long timeoutMs) + /** + * Check whether the connection is alive or not. + * + * @param connection the connection to test + * @param timeoutMs the timeout before we consider the test a failure + * @return true if the connection is alive, false if it is not alive or we timed out + */ + private boolean isConnectionAlive(final Connection connection, long timeoutMs) { + // Set a realistic minimum timeout + if (timeoutMs < 500) + { + timeoutMs = 500; + } + try { if (jdbc4ConnectionTest) @@ -311,12 +351,13 @@ public class HikariPool implements HikariPoolMBean try { statement.executeQuery(configuration.getConnectionTestQuery()); - return true; } finally { statement.close(); } + + return true; } catch (SQLException e) { @@ -325,6 +366,11 @@ public class HikariPool implements HikariPoolMBean } } + /** + * Permanently close a connection. + * + * @param connectionProxy the connection to actually close + */ private void closeConnection(IHikariConnectionProxy connectionProxy) { try @@ -338,6 +384,9 @@ public class HikariPool implements HikariPoolMBean } } + /** + * Register the pool and pool configuration objects with the MBean server. + */ private void registerMBean() { try @@ -362,6 +411,9 @@ public class HikariPool implements HikariPoolMBean } } + /** + * The house keeping task to retire idle and maxAge connections. + */ private class HouseKeeper extends TimerTask { public void run() diff --git a/src/main/java/com/zaxxer/hikari/javassist/HikariClassTransformer.java b/src/main/java/com/zaxxer/hikari/javassist/HikariClassTransformer.java index 46613dc1..8c8fa8de 100644 --- a/src/main/java/com/zaxxer/hikari/javassist/HikariClassTransformer.java +++ b/src/main/java/com/zaxxer/hikari/javassist/HikariClassTransformer.java @@ -112,19 +112,19 @@ public class HikariClassTransformer implements ClassFileTransformer } else if (iface.equals("java.sql.PreparedStatement")) { - return transformPreparedStatement(classFile); + return transformClass(classFile, "com.zaxxer.hikari.proxy.PreparedStatementProxy", "com.zaxxer.hikari.proxy.IHikariStatementProxy"); } else if (iface.equals("java.sql.CallableStatement")) { - return transformCallableStatement(classFile); + return transformClass(classFile, "com.zaxxer.hikari.proxy.CallableStatementProxy", "com.zaxxer.hikari.proxy.IHikariStatementProxy"); } else if (iface.equals("java.sql.Statement")) { - return transformStatement(classFile); + return transformClass(classFile, "com.zaxxer.hikari.proxy.StatementProxy", "com.zaxxer.hikari.proxy.IHikariStatementProxy"); } else if (iface.equals("java.sql.ResultSet")) { - return transformResultSet(classFile); + return transformClass(classFile, "com.zaxxer.hikari.proxy.ResultSetProxy", "com.zaxxer.hikari.proxy.IHikariResultSetProxy"); } } @@ -161,6 +161,7 @@ public class HikariClassTransformer implements ClassFileTransformer copyFields(proxy, target); copyMethods(proxy, target, classFile); mergeClassInitializers(proxy, target, classFile); + specialConnectionInjectCloseCheck(target); injectTryCatch(target); for (CtConstructor constructor : target.getConstructors()) @@ -175,85 +176,16 @@ public class HikariClassTransformer implements ClassFileTransformer /** * @param classFile */ - private byte[] transformPreparedStatement(ClassFile classFile) throws Exception + private byte[] transformClass(ClassFile classFile, String proxyClassName, String intfName) throws Exception { String className = classFile.getName(); CtClass target = classPool.getCtClass(className); - CtClass intf = classPool.get("com.zaxxer.hikari.proxy.IHikariStatementProxy"); + CtClass intf = classPool.get(intfName); target.addInterface(intf); LOGGER.debug("Added interface {} to {}", intf.getName(), className); - CtClass proxy = classPool.get("com.zaxxer.hikari.proxy.PreparedStatementProxy"); - - copyFields(proxy, target); - copyMethods(proxy, target, classFile); - mergeClassInitializers(proxy, target, classFile); - injectTryCatch(target); - - target.debugWriteFile("/tmp"); - return target.toBytecode(); - } - - /** - * @param classFile - */ - private byte[] transformCallableStatement(ClassFile classFile) throws Exception - { - String className = classFile.getName(); - CtClass target = classPool.getCtClass(className); - - CtClass intf = classPool.get("com.zaxxer.hikari.proxy.IHikariStatementProxy"); - target.addInterface(intf); - LOGGER.debug("Added interface {} to {}", intf.getName(), className); - - CtClass proxy = classPool.get("com.zaxxer.hikari.proxy.CallableStatementProxy"); - - copyFields(proxy, target); - copyMethods(proxy, target, classFile); - mergeClassInitializers(proxy, target, classFile); - injectTryCatch(target); - - target.debugWriteFile("/tmp"); - return target.toBytecode(); - } - - /** - * @param classFile - */ - private byte[] transformStatement(ClassFile classFile) throws Exception - { - String className = classFile.getName(); - CtClass target = classPool.getCtClass(className); - - CtClass intf = classPool.get("com.zaxxer.hikari.proxy.IHikariStatementProxy"); - target.addInterface(intf); - LOGGER.debug("Added interface {} to {}", intf.getName(), className); - - CtClass proxy = classPool.get("com.zaxxer.hikari.proxy.StatementProxy"); - - copyFields(proxy, target); - copyMethods(proxy, target, classFile); - mergeClassInitializers(proxy, target, classFile); - injectTryCatch(target); - - target.debugWriteFile("/tmp"); - return target.toBytecode(); - } - - /** - * @param classFile - */ - private byte[] transformResultSet(ClassFile classFile) throws Exception - { - String className = classFile.getName(); - CtClass target = classPool.getCtClass(className); - - CtClass intf = classPool.get("com.zaxxer.hikari.proxy.IHikariResultSetProxy"); - target.addInterface(intf); - LOGGER.debug("Added interface {} to {}", intf.getName(), className); - - CtClass proxy = classPool.get("com.zaxxer.hikari.proxy.ResultSetProxy"); + CtClass proxy = classPool.get(proxyClassName); copyFields(proxy, target); copyMethods(proxy, target, classFile); @@ -359,6 +291,11 @@ public class HikariClassTransformer implements ClassFileTransformer continue; } + if (method.getMethodInfo().getCodeAttribute() == null) + { + continue; + } + for (CtClass exception : method.getExceptionTypes()) { if ("java.sql.SQLException".equals(exception.getName())) // only add try..catch to methods throwing SQLException @@ -370,6 +307,32 @@ public class HikariClassTransformer implements ClassFileTransformer } } + private void specialConnectionInjectCloseCheck(CtClass destClass) throws Exception + { + for (CtMethod method : destClass.getMethods()) + { + if ((method.getModifiers() & Modifier.PUBLIC) != Modifier.PUBLIC || // only public methods + method.getAnnotation(HikariInject.class) != null) // ignore methods we've injected, they already try..catch + { + continue; + } + + if (method.getMethodInfo().getCodeAttribute() == null) + { + continue; + } + + for (CtClass exception : method.getExceptionTypes()) + { + if ("java.sql.SQLException".equals(exception.getName())) // only add check to methods throwing SQLException + { + method.insertBefore("if (_isClosed) { throw new java.sql.SQLException(\"Connection is closed\"); }"); + break; + } + } + } + } + /** * */ diff --git a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java index 07377781..bec04bc4 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java @@ -26,7 +26,6 @@ import java.util.HashSet; import java.util.Set; import java.util.Timer; import java.util.TimerTask; -import java.util.concurrent.atomic.AtomicBoolean; import com.zaxxer.hikari.HikariPool; import com.zaxxer.hikari.javassist.HikariInject; @@ -55,7 +54,7 @@ public class ConnectionProxy extends HikariProxyBase implements IHikariConnectio @HikariInject private static final Set SPECIAL_ERRORS; @HikariInject private ArrayList _openStatements; - @HikariInject private AtomicBoolean _isClosed; + @HikariInject private volatile boolean _isClosed; @HikariInject private HikariPool _parentPool; protected final Connection delegate; @@ -96,7 +95,7 @@ public class ConnectionProxy extends HikariProxyBase implements IHikariConnectio // If the connection is not closed. If it is closed, it means this is being // called back as a result of the close() method below in which case we // will clear the openStatements collection en mass. - if (!_isClosed.get()) + if (!_isClosed) { _openStatements.remove(statement); } @@ -129,7 +128,7 @@ public class ConnectionProxy extends HikariProxyBase implements IHikariConnectio @HikariInject public void unclose() { - _isClosed.set(false); + _isClosed = false; } @HikariInject @@ -175,10 +174,18 @@ public class ConnectionProxy extends HikariProxyBase implements IHikariConnectio private void __init() { _openStatements = new ArrayList(64); - _isClosed = new AtomicBoolean(); _creationTime = _lastAccess = System.currentTimeMillis(); } + @HikariInject + private void checkClosed() throws SQLException + { + if (_isClosed) + { + throw new SQLException("Connection is closed"); + } + } + public final Connection getDelegate() { return delegate; @@ -191,8 +198,9 @@ public class ConnectionProxy extends HikariProxyBase implements IHikariConnectio @HikariInject public void close() throws SQLException { - if (_isClosed.compareAndSet(false, true)) + if (!_isClosed) { + _isClosed = true; if (_leakTask != null) { _leakTask.cancel(); @@ -201,7 +209,6 @@ public class ConnectionProxy extends HikariProxyBase implements IHikariConnectio try { - // Faster than an iterator for (int i = _openStatements.size() - 1; i >= 0; i--) { @@ -223,12 +230,13 @@ public class ConnectionProxy extends HikariProxyBase implements IHikariConnectio @HikariInject public boolean isClosed() throws SQLException { - return _isClosed.get(); + return _isClosed; } @HikariInject public Statement createStatement() throws SQLException { + checkClosed(); try { Statement statementProxy = __createStatement(); @@ -246,6 +254,7 @@ public class ConnectionProxy extends HikariProxyBase implements IHikariConnectio @HikariInject public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { + checkClosed(); try { Statement statementProxy = __createStatement(resultSetType, resultSetConcurrency); @@ -263,6 +272,7 @@ public class ConnectionProxy extends HikariProxyBase implements IHikariConnectio @HikariInject public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { + checkClosed(); try { Statement statementProxy = __createStatement(resultSetType, resultSetConcurrency, resultSetHoldability); @@ -280,6 +290,7 @@ public class ConnectionProxy extends HikariProxyBase implements IHikariConnectio @HikariInject public CallableStatement prepareCall(String sql) throws SQLException { + checkClosed(); try { CallableStatement statementProxy = __prepareCall(sql); @@ -297,6 +308,7 @@ public class ConnectionProxy extends HikariProxyBase implements IHikariConnectio @HikariInject public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { + checkClosed(); try { CallableStatement statementProxy = __prepareCall(sql, resultSetType, resultSetConcurrency); @@ -314,6 +326,7 @@ public class ConnectionProxy extends HikariProxyBase implements IHikariConnectio @HikariInject public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { + checkClosed(); try { CallableStatement statementProxy = __prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); @@ -331,6 +344,7 @@ public class ConnectionProxy extends HikariProxyBase implements IHikariConnectio @HikariInject public PreparedStatement prepareStatement(String sql) throws SQLException { + checkClosed(); try { PreparedStatement statementProxy = __prepareStatement(sql); @@ -348,6 +362,7 @@ public class ConnectionProxy extends HikariProxyBase implements IHikariConnectio @HikariInject public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { + checkClosed(); try { PreparedStatement statementProxy = __prepareStatement(sql, autoGeneratedKeys); @@ -365,6 +380,7 @@ public class ConnectionProxy extends HikariProxyBase implements IHikariConnectio @HikariInject public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { + checkClosed(); try { PreparedStatement statementProxy = __prepareStatement(sql, resultSetType, resultSetConcurrency); @@ -382,6 +398,7 @@ public class ConnectionProxy extends HikariProxyBase implements IHikariConnectio @HikariInject public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { + checkClosed(); try { PreparedStatement statementProxy = __prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability); @@ -399,6 +416,7 @@ public class ConnectionProxy extends HikariProxyBase implements IHikariConnectio @HikariInject public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { + checkClosed(); try { PreparedStatement statementProxy = __prepareStatement(sql, columnIndexes); @@ -416,6 +434,7 @@ public class ConnectionProxy extends HikariProxyBase implements IHikariConnectio @HikariInject public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { + checkClosed(); try { PreparedStatement statementProxy = __prepareStatement(sql, columnNames); diff --git a/src/main/java/com/zaxxer/hikari/proxy/HikariProxyBase.java b/src/main/java/com/zaxxer/hikari/proxy/HikariProxyBase.java index 87605f7a..d4471286 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/HikariProxyBase.java +++ b/src/main/java/com/zaxxer/hikari/proxy/HikariProxyBase.java @@ -18,15 +18,12 @@ package com.zaxxer.hikari.proxy; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.sql.SQLException; /** * @author Brett Wooldridge */ public abstract class HikariProxyBase { - protected abstract SQLException checkException(SQLException e); - protected static boolean isWrapperFor(Object obj, Class param) { try diff --git a/src/test/java/com/zaxxer/hikari/CreationTest.java b/src/test/java/com/zaxxer/hikari/CreationTest.java index db91d8d5..04ce7542 100644 --- a/src/test/java/com/zaxxer/hikari/CreationTest.java +++ b/src/test/java/com/zaxxer/hikari/CreationTest.java @@ -39,6 +39,7 @@ public class CreationTest { HikariConfig config = new HikariConfig(); config.setMinimumPoolSize(1); + config.setMaximumPoolSize(1); config.setAcquireIncrement(1); config.setConnectionTestQuery("VALUES 1"); config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource"); @@ -79,6 +80,7 @@ public class CreationTest { HikariConfig config = new HikariConfig(); config.setMinimumPoolSize(1); + config.setMaximumPoolSize(1); config.setAcquireIncrement(1); config.setMaxLifetime(500); config.setConnectionTestQuery("VALUES 1"); @@ -118,6 +120,7 @@ public class CreationTest { HikariConfig config = new HikariConfig(); config.setMinimumPoolSize(1); + config.setMaximumPoolSize(1); config.setAcquireIncrement(1); config.setMaxLifetime(500); config.setConnectionTestQuery("VALUES 1"); diff --git a/src/test/java/com/zaxxer/hikari/performance/Benchmark2.java b/src/test/java/com/zaxxer/hikari/performance/Benchmark2.java index d277a1dd..55cf8db5 100644 --- a/src/test/java/com/zaxxer/hikari/performance/Benchmark2.java +++ b/src/test/java/com/zaxxer/hikari/performance/Benchmark2.java @@ -105,7 +105,7 @@ public class Benchmark2 HikariDataSource ds = new HikariDataSource(config); return ds; } - + private DataSource setupBone() { try