From 3b23c844130d7435c8c07e97724f194f2aa51513 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Mon, 10 Feb 2014 13:10:36 +0900 Subject: [PATCH 01/22] Update snapshot version to 1.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d07e793c8..dbdc8d858 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.zaxxer HikariCP - 1.2.10-SNAPSHOT + 1.3.0-SNAPSHOT bundle HikariCP From e39fc44ad96a15ee8fe6eca04aafa27f95728af6 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Mon, 10 Feb 2014 18:18:35 +0900 Subject: [PATCH 02/22] Add log when pool is shutdown. --- src/main/java/com/zaxxer/hikari/HikariPool.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/zaxxer/hikari/HikariPool.java b/src/main/java/com/zaxxer/hikari/HikariPool.java index 0bd0183d9..6ca5a7b2f 100644 --- a/src/main/java/com/zaxxer/hikari/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/HikariPool.java @@ -244,6 +244,8 @@ public void releaseConnection(IHikariConnectionProxy connectionProxy) void shutdown() { + LOGGER.info("HikariCP pool " + configuration.getPoolName() + " is being shutdown."); + shutdown = true; houseKeepingTimer.cancel(); From 867fc0300332490934e25c5f69495026cb77f66c Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Mon, 10 Feb 2014 18:38:53 +0900 Subject: [PATCH 03/22] Added logging for broken connections. --- .../java/com/zaxxer/hikari/proxy/ConnectionProxy.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java index 698043542..0e5f2da6d 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java @@ -26,6 +26,9 @@ import java.util.Timer; import java.util.TimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.zaxxer.hikari.HikariPool; import com.zaxxer.hikari.util.FastStatementList; @@ -36,6 +39,8 @@ */ public abstract class ConnectionProxy implements IHikariConnectionProxy { + private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionProxy.class); + private static final Set SQL_ERRORS; protected final Connection delegate; @@ -61,7 +66,6 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy SQL_ERRORS.add("57P01"); // ADMIN SHUTDOWN SQL_ERRORS.add("57P02"); // CRASH SHUTDOWN SQL_ERRORS.add("57P03"); // CANNOT CONNECT NOW - SQL_ERRORS.add("57P02"); // CRASH SHUTDOWN SQL_ERRORS.add("01002"); // SQL92 disconnect error } @@ -137,6 +141,10 @@ public final void checkException(SQLException sqle) if (sqlState != null) { forceClose |= sqlState.startsWith("08") | SQL_ERRORS.contains(sqlState); + if (forceClose) + { + LOGGER.warn("Connection {} marked as broken because of SQLSTATE({})", delegate.toString(), sqlState); + } } } From d403b01afa869ee49ab7eb4b7e3c6c24fb9ad138 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Tue, 11 Feb 2014 19:49:42 +0900 Subject: [PATCH 04/22] Readability improvement. --- .../com/zaxxer/hikari/proxy/JavassistProxyFactory.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactory.java b/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactory.java index 2fecf7d91..e65a18aba 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactory.java +++ b/src/main/java/com/zaxxer/hikari/proxy/JavassistProxyFactory.java @@ -101,20 +101,21 @@ private void modifyProxyFactory() throws Exception CtClass proxyCt = classPool.getCtClass("com.zaxxer.hikari.proxy.ProxyFactory"); for (CtMethod method : proxyCt.getMethods()) { + String methodName = method.getName(); StringBuilder call = new StringBuilder("{"); - if ("getProxyConnection".equals(method.getName())) + if ("getProxyConnection".equals(methodName)) { call.append("return new ").append(packageName).append(".ConnectionJavassistProxy($$);"); } - else if ("getProxyStatement".equals(method.getName())) + else if ("getProxyStatement".equals(methodName)) { call.append("return new ").append(packageName).append(".StatementJavassistProxy($$);"); } - else if ("getProxyPreparedStatement".equals(method.getName())) + else if ("getProxyPreparedStatement".equals(methodName)) { call.append("return new ").append(packageName).append(".PreparedStatementJavassistProxy($$);"); } - else if ("getProxyCallableStatement".equals(method.getName())) + else if ("getProxyCallableStatement".equals(methodName)) { call.append("return new ").append(packageName).append(".CallableStatementJavassistProxy($$);"); } From e3a3a1ab3d9a28083a99073300d51f3ca9220ba5 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Tue, 11 Feb 2014 19:50:28 +0900 Subject: [PATCH 05/22] Start of experimental implementation of a concurrent bag. See http://geekswithblogs.net/simonc/archive/2012/03/26/inside-the-concurrent-collections-concurrentbag.aspx --- .../com/zaxxer/hikari/util/ConcurrentBag.java | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java diff --git a/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java b/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java new file mode 100644 index 000000000..f9a72b3e4 --- /dev/null +++ b/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java @@ -0,0 +1,117 @@ +package com.zaxxer.hikari.util; + +import java.lang.reflect.Field; +import java.util.LinkedList; +import java.util.concurrent.locks.ReentrantLock; + +public class ConcurrentBag +{ + private static sun.misc.Unsafe unsafe = getUnsafe(); + + private LinkedList> sharedList; + + private ThreadLocal> threadList = new ThreadLocal>() { + protected java.util.LinkedList initialValue() + { + LinkedList list = new LinkedList(); + sharedList.add(list); + return list; + } + }; + + public ConcurrentBag() + { + sharedList = new LinkedList<>(); + } + + @SuppressWarnings("restriction") + private static sun.misc.Unsafe getUnsafe() + { + try + { + Field f = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (sun.misc.Unsafe) f.get(null); + } + catch (Exception e) + { + throw new RuntimeException("Cannot access sun.misc.Unsafe"); + } + } + + private static class SinglyLinkedList + { + private ReentrantLock putLock = new ReentrantLock(); + private ReentrantLock takeLock = new ReentrantLock(); + + Node head; + Node tail; + + void add(T value) + { + Node node = new Node(value); + final ReentrantLock putLock = this.putLock; + putLock.lock(); + try + { + if (head == null) + { + head = tail = node; + } + else + { + tail.next = node; + } + } + finally + { + putLock.unlock(); + } + } + + void remove(T value) + { + final ReentrantLock putLock = this.putLock; + final ReentrantLock takeLock = this.takeLock; + putLock.lock(); + takeLock.lock(); + try + { + Node node = head; + Node prev = null; + while (node != null) + { + if (node.value == value) + { + if (prev == null) + { + head = node; + } + else + { + prev.next = node.next; + } + break; + } + node = node.next; + } + } + finally + { + takeLock.unlock(); + putLock.unlock(); + } + } + } + + private static class Node + { + E value; + Node next; + + Node(E value) + { + this.value = value; + } + } +} From 4bf148f6d07efb03acc495ff52f1fea9dca649e1 Mon Sep 17 00:00:00 2001 From: "Flavio W. Brasil" Date: Tue, 11 Feb 2014 21:49:03 +0100 Subject: [PATCH 06/22] fix fast statement list overflow inconsistency --- src/main/java/com/zaxxer/hikari/util/FastStatementList.java | 1 + src/test/java/com/zaxxer/hikari/TestFastStatementList.java | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/main/java/com/zaxxer/hikari/util/FastStatementList.java b/src/main/java/com/zaxxer/hikari/util/FastStatementList.java index 951fc52ce..b464ba351 100644 --- a/src/main/java/com/zaxxer/hikari/util/FastStatementList.java +++ b/src/main/java/com/zaxxer/hikari/util/FastStatementList.java @@ -62,6 +62,7 @@ public void add(Statement element) catch (ArrayIndexOutOfBoundsException oob) { // overflow-conscious code + size--; int oldCapacity = elementData.length; int newCapacity = oldCapacity << 2; Statement[] newElementData = new Statement[newCapacity]; diff --git a/src/test/java/com/zaxxer/hikari/TestFastStatementList.java b/src/test/java/com/zaxxer/hikari/TestFastStatementList.java index 11aade42c..b6142a217 100644 --- a/src/test/java/com/zaxxer/hikari/TestFastStatementList.java +++ b/src/test/java/com/zaxxer/hikari/TestFastStatementList.java @@ -1,5 +1,6 @@ package com.zaxxer.hikari; +import org.junit.Assert; import org.junit.Test; import com.zaxxer.hikari.performance.StubStatement; @@ -15,5 +16,9 @@ public void testOverflow() { list.add(new StubStatement()); } + for (int i = 0; i < 100; i++) + { + Assert.assertNotNull(list.get(i)); + } } } From 71ef0b6b8dd39e6e4c2597accac36c8154d5f01b Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Wed, 12 Feb 2014 19:03:33 +0900 Subject: [PATCH 07/22] Adjust version number in the benchmark script. --- benchmark.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark.sh b/benchmark.sh index 618a136a0..f209943bb 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -18,7 +18,7 @@ CLASSPATH=$CLASSPATH:~/.m2/repository/commons-logging/commons-logging/1.1.3/comm CLASSPATH=$CLASSPATH:~/.m2/repository/commons-logging/commons-logging-api/1.1/commons-logging-api-1.1.jar CLASSPATH=$CLASSPATH:$JAVA_HOME/lib/tools.jar -CLASSPATH=$CLASSPATH:./target/HikariCP-1.2.9-SNAPSHOT.jar +CLASSPATH=$CLASSPATH:./target/HikariCP-1.3.0-SNAPSHOT.jar CLASSPATH=$CLASSPATH:./target/test-classes java -classpath $CLASSPATH \ From 522717f885154660e7fc60846c22827e4b99a042 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Wed, 12 Feb 2014 19:09:38 +0900 Subject: [PATCH 08/22] New concurrent connection container (moved away from LinkedBlockingQueue and LinkedTransferQueue). --- .../java/com/zaxxer/hikari/HikariPool.java | 41 ++-- .../hikari/SpecializedConcurrentBag.java | 178 ++++++++++++++++++ .../com/zaxxer/hikari/util/ConcurrentBag.java | 117 ------------ 3 files changed, 196 insertions(+), 140 deletions(-) create mode 100644 src/main/java/com/zaxxer/hikari/SpecializedConcurrentBag.java delete mode 100644 src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java diff --git a/src/main/java/com/zaxxer/hikari/HikariPool.java b/src/main/java/com/zaxxer/hikari/HikariPool.java index 6ca5a7b2f..354db8697 100644 --- a/src/main/java/com/zaxxer/hikari/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/HikariPool.java @@ -19,9 +19,9 @@ import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; +import java.util.List; import java.util.Timer; import java.util.TimerTask; -import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -49,7 +49,7 @@ public final class HikariPool implements HikariPoolMBean private final IConnectionCustomizer connectionCustomizer; private final HikariConfig configuration; - private final LinkedBlockingQueue idleConnections; + private final SpecializedConcurrentBag idleConnections; private final Timer houseKeepingTimer; @@ -79,7 +79,7 @@ public final class HikariPool implements HikariPoolMBean this.idleConnectionCount = new AtomicInteger(); this.awaitingConnection = new AtomicInteger(); this.backgroundFillQueued = new AtomicBoolean(); - this.idleConnections = new LinkedBlockingQueue(); + this.idleConnections = new SpecializedConcurrentBag(); this.jdbc4ConnectionTest = configuration.isJdbc4ConnectionTest(); this.leakDetectionThreshold = configuration.getLeakDetectionThreshold(); @@ -249,16 +249,8 @@ void shutdown() shutdown = true; houseKeepingTimer.cancel(); - while (true) - { - IHikariConnectionProxy connection = idleConnections.poll(); - if (connection == null) - { - break; - } - closeConnection(connection); - } - + closeIdleConnections(); + HikariMBeanElf.unregisterMBeans(configuration, this); } @@ -293,13 +285,13 @@ public int getThreadsAwaitingConnection() /** {@inheritDoc} */ public void closeIdleConnections() { - final int idleCount = idleConnectionCount.get(); - for (int i = 0; i < idleCount; i++) + List list = idleConnections.values(SpecializedConcurrentBag.NOT_IN_USE); + for (IHikariConnectionProxy connectionProxy : list) { - IHikariConnectionProxy connectionProxy = idleConnections.poll(); + connectionProxy = idleConnections.checkout(connectionProxy); if (connectionProxy == null) { - break; + continue; } idleConnectionCount.decrementAndGet(); @@ -417,7 +409,7 @@ private void addConnection() { idleConnectionCount.incrementAndGet(); totalConnections.incrementAndGet(); - idleConnections.add(proxyConnection); + idleConnections.offer(proxyConnection); } break; } @@ -524,6 +516,10 @@ private void closeConnection(IHikariConnectionProxy connectionProxy) { return; } + finally + { + idleConnections.remove(connectionProxy); + } } private void logPoolState(String... prefix) @@ -549,14 +545,13 @@ public void run() final long now = System.currentTimeMillis(); final long idleTimeout = configuration.getIdleTimeout(); final long maxLifetime = configuration.getMaxLifetime(); - final int idleCount = idleConnectionCount.get(); - for (int i = 0; i < idleCount; i++) + for (IHikariConnectionProxy connectionProxy : idleConnections.values(SpecializedConcurrentBag.NOT_IN_USE)) { - IHikariConnectionProxy connectionProxy = idleConnections.poll(); + connectionProxy = idleConnections.checkout(connectionProxy); if (connectionProxy == null) { - break; + continue; } idleConnectionCount.decrementAndGet(); @@ -570,7 +565,7 @@ public void run() else { idleConnectionCount.incrementAndGet(); - idleConnections.add(connectionProxy); + idleConnections.checkin(connectionProxy); } } diff --git a/src/main/java/com/zaxxer/hikari/SpecializedConcurrentBag.java b/src/main/java/com/zaxxer/hikari/SpecializedConcurrentBag.java new file mode 100644 index 000000000..018900475 --- /dev/null +++ b/src/main/java/com/zaxxer/hikari/SpecializedConcurrentBag.java @@ -0,0 +1,178 @@ +package com.zaxxer.hikari; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicStampedReference; +import java.util.concurrent.locks.AbstractQueuedLongSynchronizer; + +public class SpecializedConcurrentBag +{ + static final int NOT_IN_USE = 0; + static final int IN_USE = 1; + static final int REMOVED = -1; + + private ConcurrentHashMap> map; + private Synchronizer synchronizer; + + private ThreadLocal>> threadList = new ThreadLocal>>() + { + protected LinkedList> initialValue() + { + return new LinkedList<>(); + } + }; + + public SpecializedConcurrentBag() + { + map = new ConcurrentHashMap<>(); + synchronizer = new Synchronizer(); + } + + public T poll(long timeout, TimeUnit timeUnit) throws InterruptedException + { + // Try the thread-local list first + LinkedList> list = threadList.get(); + while (!list.isEmpty()) + { + AtomicStampedReference stampedReference = list.removeLast(); + final T reference = stampedReference.getReference(); + if (stampedReference.compareAndSet(reference, reference, NOT_IN_USE, IN_USE)) + { + return reference; + } + } + + timeout = timeUnit.toNanos(timeout); + do { + final long start = System.nanoTime(); + for (AtomicStampedReference stampedReference : map.values()) + { + final T reference = stampedReference.getReference(); + if (stampedReference.compareAndSet(reference, reference, NOT_IN_USE, IN_USE)) + { + return reference; + } + } + + synchronizer.tryAcquireSharedNanos(1, timeout); + + timeout -= (System.nanoTime() - start); + } while (timeout > 0); + + return null; + } + + public boolean offer(T value) + { + LinkedList> list = threadList.get(); + AtomicStampedReference stampedReference = map.get(value); + if (stampedReference == null) + { + stampedReference = new AtomicStampedReference(value, NOT_IN_USE); + map.put(value, stampedReference); + list.addLast(stampedReference); + } + else + { + final T reference = stampedReference.getReference(); + if (stampedReference.compareAndSet(reference, reference, IN_USE, NOT_IN_USE)) + { + list.addLast(stampedReference); + } + } + + synchronizer.releaseShared(1); + + return true; + } + + public void remove(T value) + { + AtomicStampedReference stampedReference = map.get(value); + if (stampedReference != null) + { + stampedReference.set(stampedReference.getReference(), REMOVED); + map.remove(value); + } + } + + public List values(int state) + { + ArrayList list = new ArrayList<>(map.size()); + for (AtomicStampedReference stampedReference : map.values()) + { + if (stampedReference.getStamp() == state) + { + list.add(stampedReference.getReference()); + } + } + + return list; + } + + T checkout(T value) + { + AtomicStampedReference stampedReference = map.get(value); + if (stampedReference != null && stampedReference.compareAndSet(stampedReference.getReference(), stampedReference.getReference(), NOT_IN_USE, IN_USE)) + { + return value; + } + + return null; + } + + void checkin(T value) + { + AtomicStampedReference stampedReference = map.get(value); + if (stampedReference != null) + { + final T reference = stampedReference.getReference(); + stampedReference.compareAndSet(reference, reference, IN_USE, NOT_IN_USE); + synchronizer.releaseShared(1); + } + } + + private static class Synchronizer extends AbstractQueuedLongSynchronizer + { + private static final long serialVersionUID = 104753538004341218L; + + private static ThreadLocal startTimeStamp = new ThreadLocal() { + protected Long initialValue() + { + return System.nanoTime(); + } + }; + + @Override + protected long tryAcquireShared(long arg) + { + Long waitStart = startTimeStamp.get(); + + // fairness + if (hasQueuedPredecessors()) + { + return -1; + } + + if (getState() > waitStart) + { + startTimeStamp.remove(); + return 1; + } + + return -1; + } + + /** {@inheritDoc} */ + @Override + protected boolean tryReleaseShared(long arg) + { + setState(System.nanoTime()); + + return true; + } + } +} diff --git a/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java b/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java deleted file mode 100644 index f9a72b3e4..000000000 --- a/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.zaxxer.hikari.util; - -import java.lang.reflect.Field; -import java.util.LinkedList; -import java.util.concurrent.locks.ReentrantLock; - -public class ConcurrentBag -{ - private static sun.misc.Unsafe unsafe = getUnsafe(); - - private LinkedList> sharedList; - - private ThreadLocal> threadList = new ThreadLocal>() { - protected java.util.LinkedList initialValue() - { - LinkedList list = new LinkedList(); - sharedList.add(list); - return list; - } - }; - - public ConcurrentBag() - { - sharedList = new LinkedList<>(); - } - - @SuppressWarnings("restriction") - private static sun.misc.Unsafe getUnsafe() - { - try - { - Field f = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); - f.setAccessible(true); - return (sun.misc.Unsafe) f.get(null); - } - catch (Exception e) - { - throw new RuntimeException("Cannot access sun.misc.Unsafe"); - } - } - - private static class SinglyLinkedList - { - private ReentrantLock putLock = new ReentrantLock(); - private ReentrantLock takeLock = new ReentrantLock(); - - Node head; - Node tail; - - void add(T value) - { - Node node = new Node(value); - final ReentrantLock putLock = this.putLock; - putLock.lock(); - try - { - if (head == null) - { - head = tail = node; - } - else - { - tail.next = node; - } - } - finally - { - putLock.unlock(); - } - } - - void remove(T value) - { - final ReentrantLock putLock = this.putLock; - final ReentrantLock takeLock = this.takeLock; - putLock.lock(); - takeLock.lock(); - try - { - Node node = head; - Node prev = null; - while (node != null) - { - if (node.value == value) - { - if (prev == null) - { - head = node; - } - else - { - prev.next = node.next; - } - break; - } - node = node.next; - } - } - finally - { - takeLock.unlock(); - putLock.unlock(); - } - } - } - - private static class Node - { - E value; - Node next; - - Node(E value) - { - this.value = value; - } - } -} From ce2d5a314126ff8c7abed1292e1a9b89daef7c76 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Wed, 12 Feb 2014 22:35:22 +0900 Subject: [PATCH 09/22] Even more detailed log for broken connections --- src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java index 0e5f2da6d..6bc6af4d4 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java @@ -143,7 +143,7 @@ public final void checkException(SQLException sqle) forceClose |= sqlState.startsWith("08") | SQL_ERRORS.contains(sqlState); if (forceClose) { - LOGGER.warn("Connection {} marked as broken because of SQLSTATE({})", delegate.toString(), sqlState); + LOGGER.warn("Connection {} marked as broken because of SQLSTATE({}), ErrorCode({}): {}", delegate.toString(), sqlState, sqle.getErrorCode(), sqle.getNextException()); } } } From b2641afe7e5ee55a74d471415b31e31853e9afce Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Wed, 12 Feb 2014 23:11:57 +0900 Subject: [PATCH 10/22] Add license header and class-level javadoc. --- .../hikari/SpecializedConcurrentBag.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/main/java/com/zaxxer/hikari/SpecializedConcurrentBag.java b/src/main/java/com/zaxxer/hikari/SpecializedConcurrentBag.java index 018900475..844b31dce 100644 --- a/src/main/java/com/zaxxer/hikari/SpecializedConcurrentBag.java +++ b/src/main/java/com/zaxxer/hikari/SpecializedConcurrentBag.java @@ -1,3 +1,18 @@ +/* + * 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; import java.util.ArrayList; @@ -8,6 +23,19 @@ import java.util.concurrent.atomic.AtomicStampedReference; import java.util.concurrent.locks.AbstractQueuedLongSynchronizer; +/** + * This is a specialized concurrent bag that achieves superior performance + * to LinkedBlockingQueue and LinkedTransferQueue for the purposes of a + * connection pool. It uses ThreadLocal storage when possible to avoid + * locks, but resorts to scanning a common collection if there are no + * available connections in the ThreadLocal list. It is a "lock-less" + * implementation using a specialized AbstractQueuedLongSynchronizer to + * manage cross-thread signaling. + * + * @author Brett Wooldridge + * + * @param the templated type to store in the bag + */ public class SpecializedConcurrentBag { static final int NOT_IN_USE = 0; From 372f15f46e233e297089c5f15b6b22873f4ac660 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Wed, 12 Feb 2014 23:18:59 +0900 Subject: [PATCH 11/22] Merged changed from fwbrasil --- src/main/java/com/zaxxer/hikari/util/FastStatementList.java | 2 +- src/test/java/com/zaxxer/hikari/TestFastStatementList.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/util/FastStatementList.java b/src/main/java/com/zaxxer/hikari/util/FastStatementList.java index b464ba351..de8f85e41 100644 --- a/src/main/java/com/zaxxer/hikari/util/FastStatementList.java +++ b/src/main/java/com/zaxxer/hikari/util/FastStatementList.java @@ -62,7 +62,7 @@ public void add(Statement element) catch (ArrayIndexOutOfBoundsException oob) { // overflow-conscious code - size--; + size--; int oldCapacity = elementData.length; int newCapacity = oldCapacity << 2; Statement[] newElementData = new Statement[newCapacity]; diff --git a/src/test/java/com/zaxxer/hikari/TestFastStatementList.java b/src/test/java/com/zaxxer/hikari/TestFastStatementList.java index b6142a217..77f02b164 100644 --- a/src/test/java/com/zaxxer/hikari/TestFastStatementList.java +++ b/src/test/java/com/zaxxer/hikari/TestFastStatementList.java @@ -16,9 +16,10 @@ public void testOverflow() { list.add(new StubStatement()); } + for (int i = 0; i < 100; i++) { - Assert.assertNotNull(list.get(i)); + Assert.assertNotNull(list.get(i)); } } } From 51336d7c74a3ed788e7d161374869e017d92322e Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Thu, 13 Feb 2014 09:59:16 +0900 Subject: [PATCH 12/22] Cache the hashCode. --- .../com/zaxxer/hikari/proxy/ConnectionProxy.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java index 6bc6af4d4..7d6dd1931 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java @@ -59,6 +59,8 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy private StackTraceElement[] leakTrace; private TimerTask leakTask; + private final int hashCode; + // static initializer static { @@ -77,6 +79,7 @@ protected ConnectionProxy(HikariPool pool, Connection connection, int defaultIso creationTime = lastAccess = System.currentTimeMillis(); openStatements = new FastStatementList(); + hashCode = System.identityHashCode(this); } public final void untrackStatement(Object statement) @@ -148,6 +151,18 @@ public final void checkException(SQLException sqle) } } + @Override + public boolean equals(Object other) + { + return this == other; + } + + @Override + public int hashCode() + { + return hashCode; + } + protected final void checkClosed() throws SQLException { if (isClosed) From 3c60b60257a329ac39253a0ea091d2f2d20ab4d2 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Thu, 13 Feb 2014 09:59:59 +0900 Subject: [PATCH 13/22] Initialize the concurrent bag with an initial size. --- src/main/java/com/zaxxer/hikari/HikariPool.java | 2 +- .../zaxxer/hikari/SpecializedConcurrentBag.java | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/HikariPool.java b/src/main/java/com/zaxxer/hikari/HikariPool.java index 354db8697..67b0b56e8 100644 --- a/src/main/java/com/zaxxer/hikari/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/HikariPool.java @@ -79,7 +79,7 @@ public final class HikariPool implements HikariPoolMBean this.idleConnectionCount = new AtomicInteger(); this.awaitingConnection = new AtomicInteger(); this.backgroundFillQueued = new AtomicBoolean(); - this.idleConnections = new SpecializedConcurrentBag(); + this.idleConnections = new SpecializedConcurrentBag(configuration.getMaximumPoolSize()); this.jdbc4ConnectionTest = configuration.isJdbc4ConnectionTest(); this.leakDetectionThreshold = configuration.getLeakDetectionThreshold(); diff --git a/src/main/java/com/zaxxer/hikari/SpecializedConcurrentBag.java b/src/main/java/com/zaxxer/hikari/SpecializedConcurrentBag.java index 844b31dce..a49d1eecd 100644 --- a/src/main/java/com/zaxxer/hikari/SpecializedConcurrentBag.java +++ b/src/main/java/com/zaxxer/hikari/SpecializedConcurrentBag.java @@ -53,19 +53,19 @@ protected LinkedList> initialValue() } }; - public SpecializedConcurrentBag() + public SpecializedConcurrentBag(int initialCapacity) { - map = new ConcurrentHashMap<>(); - synchronizer = new Synchronizer(); + this.map = new ConcurrentHashMap<>(initialCapacity); + this.synchronizer = new Synchronizer(); } public T poll(long timeout, TimeUnit timeUnit) throws InterruptedException { // Try the thread-local list first - LinkedList> list = threadList.get(); + final LinkedList> list = threadList.get(); while (!list.isEmpty()) { - AtomicStampedReference stampedReference = list.removeLast(); + final AtomicStampedReference stampedReference = list.removeFirst(); final T reference = stampedReference.getReference(); if (stampedReference.compareAndSet(reference, reference, NOT_IN_USE, IN_USE)) { @@ -163,11 +163,11 @@ void checkin(T value) } } - private static class Synchronizer extends AbstractQueuedLongSynchronizer + private class Synchronizer extends AbstractQueuedLongSynchronizer { private static final long serialVersionUID = 104753538004341218L; - private static ThreadLocal startTimeStamp = new ThreadLocal() { + private ThreadLocal startTimeStamp = new ThreadLocal() { protected Long initialValue() { return System.nanoTime(); From 86c45dbf24276744388458ad6a9e97c90ee3107a Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Thu, 13 Feb 2014 14:01:58 +0900 Subject: [PATCH 14/22] Refactored SpecializedConcurrentBag for efficiency, moved to util. --- .../java/com/zaxxer/hikari/HikariPool.java | 7 +- .../hikari/SpecializedConcurrentBag.java | 206 ------------------ .../zaxxer/hikari/proxy/ConnectionProxy.java | 36 ++- .../hikari/proxy/IHikariConnectionProxy.java | 4 +- .../com/zaxxer/hikari/util/IBagManagable.java | 34 +++ .../hikari/util/SpecializedConcurrentBag.java | 177 +++++++++++++++ 6 files changed, 250 insertions(+), 214 deletions(-) delete mode 100644 src/main/java/com/zaxxer/hikari/SpecializedConcurrentBag.java create mode 100644 src/main/java/com/zaxxer/hikari/util/IBagManagable.java create mode 100644 src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java diff --git a/src/main/java/com/zaxxer/hikari/HikariPool.java b/src/main/java/com/zaxxer/hikari/HikariPool.java index 67b0b56e8..75e4d11b9 100644 --- a/src/main/java/com/zaxxer/hikari/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/HikariPool.java @@ -34,6 +34,7 @@ import com.zaxxer.hikari.proxy.IHikariConnectionProxy; import com.zaxxer.hikari.proxy.ProxyFactory; import com.zaxxer.hikari.util.PropertyBeanSetter; +import com.zaxxer.hikari.util.SpecializedConcurrentBag; /** * This is the primary connection pool class that provides the basic @@ -285,7 +286,7 @@ public int getThreadsAwaitingConnection() /** {@inheritDoc} */ public void closeIdleConnections() { - List list = idleConnections.values(SpecializedConcurrentBag.NOT_IN_USE); + List list = idleConnections.values(IHikariConnectionProxy.NOT_IN_USE); for (IHikariConnectionProxy connectionProxy : list) { connectionProxy = idleConnections.checkout(connectionProxy); @@ -409,7 +410,7 @@ private void addConnection() { idleConnectionCount.incrementAndGet(); totalConnections.incrementAndGet(); - idleConnections.offer(proxyConnection); + idleConnections.add(proxyConnection); } break; } @@ -546,7 +547,7 @@ public void run() final long idleTimeout = configuration.getIdleTimeout(); final long maxLifetime = configuration.getMaxLifetime(); - for (IHikariConnectionProxy connectionProxy : idleConnections.values(SpecializedConcurrentBag.NOT_IN_USE)) + for (IHikariConnectionProxy connectionProxy : idleConnections.values(IHikariConnectionProxy.NOT_IN_USE)) { connectionProxy = idleConnections.checkout(connectionProxy); if (connectionProxy == null) diff --git a/src/main/java/com/zaxxer/hikari/SpecializedConcurrentBag.java b/src/main/java/com/zaxxer/hikari/SpecializedConcurrentBag.java deleted file mode 100644 index a49d1eecd..000000000 --- a/src/main/java/com/zaxxer/hikari/SpecializedConcurrentBag.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * 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; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicStampedReference; -import java.util.concurrent.locks.AbstractQueuedLongSynchronizer; - -/** - * This is a specialized concurrent bag that achieves superior performance - * to LinkedBlockingQueue and LinkedTransferQueue for the purposes of a - * connection pool. It uses ThreadLocal storage when possible to avoid - * locks, but resorts to scanning a common collection if there are no - * available connections in the ThreadLocal list. It is a "lock-less" - * implementation using a specialized AbstractQueuedLongSynchronizer to - * manage cross-thread signaling. - * - * @author Brett Wooldridge - * - * @param the templated type to store in the bag - */ -public class SpecializedConcurrentBag -{ - static final int NOT_IN_USE = 0; - static final int IN_USE = 1; - static final int REMOVED = -1; - - private ConcurrentHashMap> map; - private Synchronizer synchronizer; - - private ThreadLocal>> threadList = new ThreadLocal>>() - { - protected LinkedList> initialValue() - { - return new LinkedList<>(); - } - }; - - public SpecializedConcurrentBag(int initialCapacity) - { - this.map = new ConcurrentHashMap<>(initialCapacity); - this.synchronizer = new Synchronizer(); - } - - public T poll(long timeout, TimeUnit timeUnit) throws InterruptedException - { - // Try the thread-local list first - final LinkedList> list = threadList.get(); - while (!list.isEmpty()) - { - final AtomicStampedReference stampedReference = list.removeFirst(); - final T reference = stampedReference.getReference(); - if (stampedReference.compareAndSet(reference, reference, NOT_IN_USE, IN_USE)) - { - return reference; - } - } - - timeout = timeUnit.toNanos(timeout); - do { - final long start = System.nanoTime(); - for (AtomicStampedReference stampedReference : map.values()) - { - final T reference = stampedReference.getReference(); - if (stampedReference.compareAndSet(reference, reference, NOT_IN_USE, IN_USE)) - { - return reference; - } - } - - synchronizer.tryAcquireSharedNanos(1, timeout); - - timeout -= (System.nanoTime() - start); - } while (timeout > 0); - - return null; - } - - public boolean offer(T value) - { - LinkedList> list = threadList.get(); - AtomicStampedReference stampedReference = map.get(value); - if (stampedReference == null) - { - stampedReference = new AtomicStampedReference(value, NOT_IN_USE); - map.put(value, stampedReference); - list.addLast(stampedReference); - } - else - { - final T reference = stampedReference.getReference(); - if (stampedReference.compareAndSet(reference, reference, IN_USE, NOT_IN_USE)) - { - list.addLast(stampedReference); - } - } - - synchronizer.releaseShared(1); - - return true; - } - - public void remove(T value) - { - AtomicStampedReference stampedReference = map.get(value); - if (stampedReference != null) - { - stampedReference.set(stampedReference.getReference(), REMOVED); - map.remove(value); - } - } - - public List values(int state) - { - ArrayList list = new ArrayList<>(map.size()); - for (AtomicStampedReference stampedReference : map.values()) - { - if (stampedReference.getStamp() == state) - { - list.add(stampedReference.getReference()); - } - } - - return list; - } - - T checkout(T value) - { - AtomicStampedReference stampedReference = map.get(value); - if (stampedReference != null && stampedReference.compareAndSet(stampedReference.getReference(), stampedReference.getReference(), NOT_IN_USE, IN_USE)) - { - return value; - } - - return null; - } - - void checkin(T value) - { - AtomicStampedReference stampedReference = map.get(value); - if (stampedReference != null) - { - final T reference = stampedReference.getReference(); - stampedReference.compareAndSet(reference, reference, IN_USE, NOT_IN_USE); - synchronizer.releaseShared(1); - } - } - - private class Synchronizer extends AbstractQueuedLongSynchronizer - { - private static final long serialVersionUID = 104753538004341218L; - - private ThreadLocal startTimeStamp = new ThreadLocal() { - protected Long initialValue() - { - return System.nanoTime(); - } - }; - - @Override - protected long tryAcquireShared(long arg) - { - Long waitStart = startTimeStamp.get(); - - // fairness - if (hasQueuedPredecessors()) - { - return -1; - } - - if (getState() > waitStart) - { - startTimeStamp.remove(); - return 1; - } - - return -1; - } - - /** {@inheritDoc} */ - @Override - protected boolean tryReleaseShared(long arg) - { - setState(System.nanoTime()); - - return true; - } - } -} diff --git a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java index 7d6dd1931..68726daa9 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java @@ -25,6 +25,7 @@ import java.util.Set; import java.util.Timer; import java.util.TimerTask; +import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,6 +49,7 @@ public abstract class ConnectionProxy implements IHikariConnectionProxy private final FastStatementList openStatements; private final HikariPool parentPool; private final int defaultIsolationLevel; + private final AtomicInteger state; private boolean isClosed; private boolean forceClose; @@ -76,12 +78,13 @@ protected ConnectionProxy(HikariPool pool, Connection connection, int defaultIso this.parentPool = pool; this.delegate = connection; this.defaultIsolationLevel = defaultIsolationLevel; + this.state = new AtomicInteger(); - creationTime = lastAccess = System.currentTimeMillis(); - openStatements = new FastStatementList(); - hashCode = System.identityHashCode(this); + this.creationTime = lastAccess = System.currentTimeMillis(); + this.openStatements = new FastStatementList(); + this.hashCode = System.identityHashCode(this); } - + public final void untrackStatement(Object statement) { // If the connection is not closed. If it is closed, it means this is being @@ -178,6 +181,31 @@ private final T trackStatement(T statement) return statement; } + // ********************************************************************** + // IBagManagable Methods + // ********************************************************************** + + /** {@inheritDoc} */ + @Override + public int getState() + { + return state.get(); + } + + /** {@inheritDoc} */ + @Override + public void setState(int newState) + { + state.set(newState); + } + + /** {@inheritDoc} */ + @Override + public boolean compareAndSetState(int expectedState, int newState) + { + return state.compareAndSet(expectedState, newState); + } + // ********************************************************************** // "Overridden" java.sql.Connection Methods // ********************************************************************** diff --git a/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java index 5a0fa6e66..a7660474e 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java @@ -20,11 +20,13 @@ import java.sql.SQLException; import java.util.Timer; +import com.zaxxer.hikari.util.IBagManagable; + /** * * @author Brett Wooldridge */ -public interface IHikariConnectionProxy extends Connection +public interface IHikariConnectionProxy extends Connection, IBagManagable { void unclose(); diff --git a/src/main/java/com/zaxxer/hikari/util/IBagManagable.java b/src/main/java/com/zaxxer/hikari/util/IBagManagable.java new file mode 100644 index 000000000..6a0c91f7b --- /dev/null +++ b/src/main/java/com/zaxxer/hikari/util/IBagManagable.java @@ -0,0 +1,34 @@ +/* + * 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; + +/** + * + * @author Brett Wooldridge + */ +public interface IBagManagable +{ + int NOT_IN_USE = 0; + int IN_USE = 1; + int REMOVED = -1; + + void setState(int newState); + + int getState(); + + boolean compareAndSetState(int expectedState, int newState); +} diff --git a/src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java b/src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java new file mode 100644 index 000000000..9ad4511e8 --- /dev/null +++ b/src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java @@ -0,0 +1,177 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.AbstractQueuedLongSynchronizer; + +import static com.zaxxer.hikari.util.IBagManagable.NOT_IN_USE; +import static com.zaxxer.hikari.util.IBagManagable.IN_USE; +import static com.zaxxer.hikari.util.IBagManagable.REMOVED; + +/** + * This is a specialized concurrent bag that achieves superior performance + * to LinkedBlockingQueue and LinkedTransferQueue for the purposes of a + * connection pool. It uses ThreadLocal storage when possible to avoid + * locks, but resorts to scanning a common collection if there are no + * available connections in the ThreadLocal list. It is a "lock-less" + * implementation using a specialized AbstractQueuedLongSynchronizer to + * manage cross-thread signaling. + * + * @author Brett Wooldridge + * + * @param the templated type to store in the bag + */ +public class SpecializedConcurrentBag +{ + private CopyOnWriteArraySet sharedList; + private Synchronizer synchronizer; + + private ThreadLocal> threadList = new ThreadLocal>() + { + protected LinkedList initialValue() + { + return new LinkedList<>(); + } + }; + + public SpecializedConcurrentBag(int initialCapacity) + { + this.sharedList = new CopyOnWriteArraySet<>(); + this.synchronizer = new Synchronizer(); + } + + public T poll(long timeout, TimeUnit timeUnit) throws InterruptedException + { + // Try the thread-local list first + final LinkedList list = threadList.get(); + while (!list.isEmpty()) + { + final T reference = list.removeFirst(); + if (reference.compareAndSetState(NOT_IN_USE, IN_USE)) + { + return reference; + } + } + + // Otherwise, scan the shared list + timeout = timeUnit.toNanos(timeout); + do { + final long startScan = System.nanoTime(); + for (T reference : sharedList) + { + if (reference.compareAndSetState(NOT_IN_USE, IN_USE)) + { + return reference; + } + } + + synchronizer.tryAcquireSharedNanos(startScan, timeout); + + timeout -= (System.nanoTime() - startScan); + } while (timeout > 0); + + return null; + } + + public void add(T value) + { + sharedList.add(value); + synchronizer.releaseShared(1); + } + + public boolean offer(T value) + { + if (value.compareAndSetState(IN_USE, NOT_IN_USE)) + { + threadList.get().addLast(value); + } + else + { + return false; + } + + synchronizer.releaseShared(1); + + return true; + } + + public void remove(T value) + { + value.setState(REMOVED); + sharedList.remove(value); + } + + public List values(int state) + { + ArrayList list = new ArrayList<>(sharedList.size()); + for (T reference : sharedList) + { + if (reference.getState() == state) + { + list.add(reference); + } + } + + return list; + } + + public T checkout(T value) + { + if (value.compareAndSetState(NOT_IN_USE, IN_USE)) + { + return value; + } + + return null; + } + + public void checkin(T value) + { + value.compareAndSetState(IN_USE, NOT_IN_USE); + synchronizer.releaseShared(1); + } + + private class Synchronizer extends AbstractQueuedLongSynchronizer + { + private static final long serialVersionUID = 104753538004341218L; + + @Override + protected long tryAcquireShared(long startScanTime) + { + // fairness + if (hasQueuedPredecessors()) + { + return -1; + } + + return getState() > startScanTime ? 1 : -1; + } + + /** {@inheritDoc} */ + @Override + protected boolean tryReleaseShared(long ignore) + { + setState(System.nanoTime()); + + return true; + } + } +} From 5419073cea366075bee4badc7b7c40781f954eb8 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Thu, 13 Feb 2014 14:08:22 +0900 Subject: [PATCH 15/22] Minor cleanup and javadoc. --- .../hikari/util/SpecializedConcurrentBag.java | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java b/src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java index 9ad4511e8..3ec6023af 100644 --- a/src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java +++ b/src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java @@ -41,21 +41,25 @@ */ public class SpecializedConcurrentBag { + private ThreadLocal> threadList; private CopyOnWriteArraySet sharedList; private Synchronizer synchronizer; - private ThreadLocal> threadList = new ThreadLocal>() - { - protected LinkedList initialValue() - { - return new LinkedList<>(); - } - }; - + /** + * Constructor. + * + * @param initialCapacity initial bag capacity + */ public SpecializedConcurrentBag(int initialCapacity) { this.sharedList = new CopyOnWriteArraySet<>(); this.synchronizer = new Synchronizer(); + this.threadList = new ThreadLocal>() { + protected LinkedList initialValue() + { + return new LinkedList<>(); + } + }; } public T poll(long timeout, TimeUnit timeUnit) throws InterruptedException @@ -71,7 +75,7 @@ public T poll(long timeout, TimeUnit timeUnit) throws InterruptedException } } - // Otherwise, scan the shared list + // Otherwise, scan the shared list ... for maximum of timeout timeout = timeUnit.toNanos(timeout); do { final long startScan = System.nanoTime(); @@ -149,7 +153,10 @@ public void checkin(T value) synchronizer.releaseShared(1); } - private class Synchronizer extends AbstractQueuedLongSynchronizer + /** + * Our private synchronizer that handles notify/wait type semantics. + */ + private static class Synchronizer extends AbstractQueuedLongSynchronizer { private static final long serialVersionUID = 104753538004341218L; From 9f5ad18f208a1aff6da739be472c42cb0b3190f5 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Thu, 13 Feb 2014 14:15:10 +0900 Subject: [PATCH 16/22] Synchronization fixes. --- .../zaxxer/hikari/util/SpecializedConcurrentBag.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java b/src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java index 3ec6023af..afd465e97 100644 --- a/src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java +++ b/src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java @@ -103,6 +103,7 @@ public void add(T value) public boolean offer(T value) { + final long offerTime = System.nanoTime(); if (value.compareAndSetState(IN_USE, NOT_IN_USE)) { threadList.get().addLast(value); @@ -112,7 +113,7 @@ public boolean offer(T value) return false; } - synchronizer.releaseShared(1); + synchronizer.releaseShared(offerTime); return true; } @@ -149,8 +150,9 @@ public T checkout(T value) public void checkin(T value) { + final long checkInTime = System.nanoTime(); value.compareAndSetState(IN_USE, NOT_IN_USE); - synchronizer.releaseShared(1); + synchronizer.releaseShared(checkInTime); } /** @@ -174,9 +176,9 @@ protected long tryAcquireShared(long startScanTime) /** {@inheritDoc} */ @Override - protected boolean tryReleaseShared(long ignore) + protected boolean tryReleaseShared(long updateTime) { - setState(System.nanoTime()); + setState(updateTime); return true; } From ff12869d4fd6ec3b99dc8a116bc3e6aab4699cdd Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Thu, 13 Feb 2014 14:16:37 +0900 Subject: [PATCH 17/22] Add a pool state log at timeout failure. --- src/main/java/com/zaxxer/hikari/HikariPool.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/zaxxer/hikari/HikariPool.java b/src/main/java/com/zaxxer/hikari/HikariPool.java index 75e4d11b9..e7d12689e 100644 --- a/src/main/java/com/zaxxer/hikari/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/HikariPool.java @@ -202,6 +202,7 @@ Connection getConnection() throws SQLException String msg = String.format("Timeout of %dms encountered waiting for connection.", configuration.getConnectionTimeout()); LOGGER.error(msg); + logPoolState("Timeout failure "); throw new SQLException(msg); } From 07bc7b89167d3866ebcf447a6fa1bc01622628be Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Sat, 15 Feb 2014 13:12:24 +0900 Subject: [PATCH 18/22] Change default size from 16 to 32, and change growth factor from 4x to 2x. --- src/main/java/com/zaxxer/hikari/util/FastStatementList.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/util/FastStatementList.java b/src/main/java/com/zaxxer/hikari/util/FastStatementList.java index de8f85e41..1de37962a 100644 --- a/src/main/java/com/zaxxer/hikari/util/FastStatementList.java +++ b/src/main/java/com/zaxxer/hikari/util/FastStatementList.java @@ -35,7 +35,7 @@ public final class FastStatementList */ public FastStatementList() { - this.elementData = new Statement[16]; + this.elementData = new Statement[32]; } /** @@ -64,7 +64,7 @@ public void add(Statement element) // overflow-conscious code size--; int oldCapacity = elementData.length; - int newCapacity = oldCapacity << 2; + int newCapacity = oldCapacity << 1; Statement[] newElementData = new Statement[newCapacity]; System.arraycopy(elementData, 0, newElementData, 0, oldCapacity); newElementData[size++] = element; From 142ae39059cd9f0b634da7add9cea4b148e359b5 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Sat, 15 Feb 2014 13:12:39 +0900 Subject: [PATCH 19/22] javadoc. --- .../com/zaxxer/hikari/util/SpecializedConcurrentBag.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java b/src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java index afd465e97..eebc83b9b 100644 --- a/src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java +++ b/src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java @@ -31,9 +31,10 @@ * to LinkedBlockingQueue and LinkedTransferQueue for the purposes of a * connection pool. It uses ThreadLocal storage when possible to avoid * locks, but resorts to scanning a common collection if there are no - * available connections in the ThreadLocal list. It is a "lock-less" - * implementation using a specialized AbstractQueuedLongSynchronizer to - * manage cross-thread signaling. + * available connections in the ThreadLocal list. Idle connections in + * ThreadLocal lists can be "stolen" when the poll()ing thread has none + * of its own. It is a "lock-less" implementation using a specialized + * AbstractQueuedLongSynchronizer to manage cross-thread signaling. * * @author Brett Wooldridge * From 6a8681bc70e9e16f9600fe84ccfdd40a466712d0 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Sun, 16 Feb 2014 22:01:10 +0900 Subject: [PATCH 20/22] Slightly cleaner fix for issue #35 --- src/main/java/com/zaxxer/hikari/util/FastStatementList.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/util/FastStatementList.java b/src/main/java/com/zaxxer/hikari/util/FastStatementList.java index 1de37962a..6241c6b5f 100644 --- a/src/main/java/com/zaxxer/hikari/util/FastStatementList.java +++ b/src/main/java/com/zaxxer/hikari/util/FastStatementList.java @@ -62,12 +62,11 @@ public void add(Statement element) catch (ArrayIndexOutOfBoundsException oob) { // overflow-conscious code - size--; int oldCapacity = elementData.length; int newCapacity = oldCapacity << 1; Statement[] newElementData = new Statement[newCapacity]; System.arraycopy(elementData, 0, newElementData, 0, oldCapacity); - newElementData[size++] = element; + newElementData[size] = element; elementData = (Statement[]) newElementData; } } From ef82b6bf35ef14c4e51aa0b4d666b583d3ce7022 Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Tue, 18 Feb 2014 09:13:06 +0900 Subject: [PATCH 21/22] Fix FastStatementList unit test failure. --- src/main/java/com/zaxxer/hikari/util/FastStatementList.java | 5 +++-- src/test/java/com/zaxxer/hikari/TestFastStatementList.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/zaxxer/hikari/util/FastStatementList.java b/src/main/java/com/zaxxer/hikari/util/FastStatementList.java index 6241c6b5f..65eee2fa0 100644 --- a/src/main/java/com/zaxxer/hikari/util/FastStatementList.java +++ b/src/main/java/com/zaxxer/hikari/util/FastStatementList.java @@ -57,7 +57,8 @@ public void add(Statement element) { try { - elementData[size++] = element; + elementData[size] = element; + size++; } catch (ArrayIndexOutOfBoundsException oob) { @@ -66,7 +67,7 @@ public void add(Statement element) int newCapacity = oldCapacity << 1; Statement[] newElementData = new Statement[newCapacity]; System.arraycopy(elementData, 0, newElementData, 0, oldCapacity); - newElementData[size] = element; + newElementData[size++] = element; elementData = (Statement[]) newElementData; } } diff --git a/src/test/java/com/zaxxer/hikari/TestFastStatementList.java b/src/test/java/com/zaxxer/hikari/TestFastStatementList.java index 77f02b164..c9aeda108 100644 --- a/src/test/java/com/zaxxer/hikari/TestFastStatementList.java +++ b/src/test/java/com/zaxxer/hikari/TestFastStatementList.java @@ -19,7 +19,7 @@ public void testOverflow() for (int i = 0; i < 100; i++) { - Assert.assertNotNull(list.get(i)); + Assert.assertNotNull("Element " + i, list.get(i)); } } } From b81c24d43c493dcae89cc7b5b24a86b5a433d12d Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Tue, 18 Feb 2014 10:19:35 +0900 Subject: [PATCH 22/22] Javadoc, renaming, and cleanup. --- .../java/com/zaxxer/hikari/HikariPool.java | 29 +- .../zaxxer/hikari/proxy/ConnectionProxy.java | 7 - .../hikari/proxy/IHikariConnectionProxy.java | 2 +- .../com/zaxxer/hikari/util/ConcurrentBag.java | 253 ++++++++++++++++++ .../com/zaxxer/hikari/util/IBagManagable.java | 34 --- .../hikari/util/SpecializedConcurrentBag.java | 187 ------------- 6 files changed, 266 insertions(+), 246 deletions(-) create mode 100644 src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java delete mode 100644 src/main/java/com/zaxxer/hikari/util/IBagManagable.java delete mode 100644 src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java diff --git a/src/main/java/com/zaxxer/hikari/HikariPool.java b/src/main/java/com/zaxxer/hikari/HikariPool.java index e7d12689e..a71a8e12f 100644 --- a/src/main/java/com/zaxxer/hikari/HikariPool.java +++ b/src/main/java/com/zaxxer/hikari/HikariPool.java @@ -34,7 +34,7 @@ import com.zaxxer.hikari.proxy.IHikariConnectionProxy; import com.zaxxer.hikari.proxy.ProxyFactory; import com.zaxxer.hikari.util.PropertyBeanSetter; -import com.zaxxer.hikari.util.SpecializedConcurrentBag; +import com.zaxxer.hikari.util.ConcurrentBag; /** * This is the primary connection pool class that provides the basic @@ -50,7 +50,7 @@ public final class HikariPool implements HikariPoolMBean private final IConnectionCustomizer connectionCustomizer; private final HikariConfig configuration; - private final SpecializedConcurrentBag idleConnections; + private final ConcurrentBag idleConnectionBag; private final Timer houseKeepingTimer; @@ -80,7 +80,7 @@ public final class HikariPool implements HikariPoolMBean this.idleConnectionCount = new AtomicInteger(); this.awaitingConnection = new AtomicInteger(); this.backgroundFillQueued = new AtomicBoolean(); - this.idleConnections = new SpecializedConcurrentBag(configuration.getMaximumPoolSize()); + this.idleConnectionBag = new ConcurrentBag(); this.jdbc4ConnectionTest = configuration.isJdbc4ConnectionTest(); this.leakDetectionThreshold = configuration.getLeakDetectionThreshold(); @@ -168,7 +168,7 @@ Connection getConnection() throws SQLException { addConnections(AddConnectionStrategy.ONLY_IF_EMPTY); - IHikariConnectionProxy connectionProxy = idleConnections.poll(timeout, TimeUnit.MILLISECONDS); + IHikariConnectionProxy connectionProxy = idleConnectionBag.borrow(timeout, TimeUnit.MILLISECONDS); if (connectionProxy == null) { // We timed out... break and throw exception @@ -232,10 +232,7 @@ public void releaseConnection(IHikariConnectionProxy connectionProxy) if (!connectionProxy.isBrokenConnection() && !shutdown) { idleConnectionCount.incrementAndGet(); - if (!idleConnections.offer(connectionProxy)) - { - closeConnection(connectionProxy); - } + idleConnectionBag.requite(connectionProxy); } else { @@ -287,11 +284,10 @@ public int getThreadsAwaitingConnection() /** {@inheritDoc} */ public void closeIdleConnections() { - List list = idleConnections.values(IHikariConnectionProxy.NOT_IN_USE); + List list = idleConnectionBag.values(ConcurrentBag.STATE_NOT_IN_USE); for (IHikariConnectionProxy connectionProxy : list) { - connectionProxy = idleConnections.checkout(connectionProxy); - if (connectionProxy == null) + if (!idleConnectionBag.reserve(connectionProxy)) { continue; } @@ -411,7 +407,7 @@ private void addConnection() { idleConnectionCount.incrementAndGet(); totalConnections.incrementAndGet(); - idleConnections.add(proxyConnection); + idleConnectionBag.add(proxyConnection); } break; } @@ -520,7 +516,7 @@ private void closeConnection(IHikariConnectionProxy connectionProxy) } finally { - idleConnections.remove(connectionProxy); + idleConnectionBag.remove(connectionProxy); } } @@ -548,10 +544,9 @@ public void run() final long idleTimeout = configuration.getIdleTimeout(); final long maxLifetime = configuration.getMaxLifetime(); - for (IHikariConnectionProxy connectionProxy : idleConnections.values(IHikariConnectionProxy.NOT_IN_USE)) + for (IHikariConnectionProxy connectionProxy : idleConnectionBag.values(ConcurrentBag.STATE_NOT_IN_USE)) { - connectionProxy = idleConnections.checkout(connectionProxy); - if (connectionProxy == null) + if (!idleConnectionBag.reserve(connectionProxy)) { continue; } @@ -567,7 +562,7 @@ public void run() else { idleConnectionCount.incrementAndGet(); - idleConnections.checkin(connectionProxy); + idleConnectionBag.unreserve(connectionProxy); } } diff --git a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java index 68726daa9..c14c40a60 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/ConnectionProxy.java @@ -192,13 +192,6 @@ public int getState() return state.get(); } - /** {@inheritDoc} */ - @Override - public void setState(int newState) - { - state.set(newState); - } - /** {@inheritDoc} */ @Override public boolean compareAndSetState(int expectedState, int newState) diff --git a/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java b/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java index a7660474e..58a7c5719 100644 --- a/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java +++ b/src/main/java/com/zaxxer/hikari/proxy/IHikariConnectionProxy.java @@ -20,7 +20,7 @@ import java.sql.SQLException; import java.util.Timer; -import com.zaxxer.hikari.util.IBagManagable; +import com.zaxxer.hikari.util.ConcurrentBag.IBagManagable; /** * diff --git a/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java b/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java new file mode 100644 index 000000000..ee43276eb --- /dev/null +++ b/src/main/java/com/zaxxer/hikari/util/ConcurrentBag.java @@ -0,0 +1,253 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.AbstractQueuedLongSynchronizer; + +/** + * This is a specialized concurrent bag that achieves superior performance + * to LinkedBlockingQueue and LinkedTransferQueue for the purposes of a + * connection pool. It uses ThreadLocal storage when possible to avoid + * locks, but resorts to scanning a common collection if there are no + * available connections in the ThreadLocal list. Idle connections in + * ThreadLocal lists can be "stolen" when the poll()ing thread has none + * of its own. It is a "lock-less" implementation using a specialized + * AbstractQueuedLongSynchronizer to manage cross-thread signaling. + * + * Note that objects that are "borrowed" from the bag are not actually + * removed from any collection, so garbage collection will not occur + * even if the reference is abandoned. Thus care must be taken to + * "requite" borrowed objects otherwise a memory leak will result. Only + * the "remove" method can completely remove an object from the bag. + * + * @author Brett Wooldridge + * + * @param the templated type to store in the bag + * @param + */ +public class ConcurrentBag +{ + public static final int STATE_NOT_IN_USE = 0; + public static final int STATE_IN_USE = 1; + private static final int STATE_REMOVED = -1; + private static final int STATE_RESERVED = -2; + + /** + * This interface must be implemented by classes wishing to be managed by + * ConcurrentBag. All implementations must be atomic with respect to state. + * The suggested implementation is via AtomicInteger using the methods + * get() and compareAndSet(). + */ + public interface IBagManagable + { + int getState(); + + boolean compareAndSetState(int expectedState, int newState); + } + + private ThreadLocal> threadList; + private CopyOnWriteArraySet sharedList; + private Synchronizer synchronizer; + + /** + * Constructor. + */ + public ConcurrentBag() + { + this.sharedList = new CopyOnWriteArraySet<>(); + this.synchronizer = new Synchronizer(); + this.threadList = new ThreadLocal>() { + protected LinkedList initialValue() + { + return new LinkedList<>(); + } + }; + } + + /** + * The method will borrow an IBagManagable from the bag, blocking for the + * specified timeout if none are available. + * + * @param timeout how long to wait before giving up, in units of unit + * @param timeUnit a TimeUnit determining how to interpret the timeout parameter + * @return a borrowed instance from the bag or null if a timeout occurs + * @throws InterruptedException if interrupted while waiting + */ + public T borrow(long timeout, TimeUnit timeUnit) throws InterruptedException + { + // Try the thread-local list first + final LinkedList list = threadList.get(); + while (!list.isEmpty()) + { + final T reference = list.removeFirst(); + if (reference.compareAndSetState(STATE_NOT_IN_USE, STATE_IN_USE)) + { + return reference; + } + } + + // Otherwise, scan the shared list ... for maximum of timeout + timeout = timeUnit.toNanos(timeout); + do { + final long startScan = System.nanoTime(); + for (T reference : sharedList) + { + if (reference.compareAndSetState(STATE_NOT_IN_USE, STATE_IN_USE)) + { + return reference; + } + } + + synchronizer.tryAcquireSharedNanos(startScan, timeout); + + timeout -= (System.nanoTime() - startScan); + } while (timeout > 0); + + return null; + } + + /** + * This method will return a borrowed object to the bag. Objects + * that are borrowed from the bag but never "requited" will result + * in a memory leak. + * + * @param value the value to return to the bag + * @throws NullPointerException if value is null + * @throws IllegalStateException if the requited value was not borrowed from the bag + */ + public void requite(T value) + { + if (value == null) + { + throw new NullPointerException("Cannot return a null value to the bag"); + } + + if (value.compareAndSetState(STATE_IN_USE, STATE_NOT_IN_USE)) + { + final long returnTime = System.nanoTime(); + threadList.get().addLast(value); + synchronizer.releaseShared(returnTime); + } + else + { + throw new IllegalStateException("Value was returned to the bag that was not borrowed"); + } + } + + /** + * Add a new object to the bag for others to borrow. + * + * @param value an object to add to the bag + */ + public void add(T value) + { + sharedList.add(value); + synchronizer.releaseShared(1); + } + + /** + * Remove a value from the bag. This method should only be called + * with objects obtained by borrow() or reserve(). + * @param value the value to remove + * @throws IllegalStateException if an attempt is made to remove an object + * from the bag that was not borrowed or reserved first + */ + public void remove(T value) + { + if (value.compareAndSetState(STATE_IN_USE, STATE_REMOVED) || value.compareAndSetState(STATE_RESERVED, STATE_REMOVED)) + { + sharedList.remove(value); + } + else + { + throw new IllegalStateException("Attempt to remove an object from the bag that was not borrowed or reserved"); + } + } + + /** + * This method provides a "snaphot" in time of the IBagManagable + * items in the bag in the specified state. It does not "lock" + * or reserve items in any way. + * + * @param state one of STATE_NOT_IN_USE or STATE_IN_USE + * @return a possibly empty list of objects having the state specified + */ + public List values(int state) + { + ArrayList list = new ArrayList<>(sharedList.size()); + if (state == STATE_IN_USE || state == STATE_NOT_IN_USE) + { + for (T reference : sharedList) + { + if (reference.getState() == state) + { + list.add(reference); + } + } + } + return list; + } + + public boolean reserve(T value) + { + return value.compareAndSetState(STATE_NOT_IN_USE, STATE_RESERVED); + } + + public void unreserve(T value) + { + final long checkInTime = System.nanoTime(); + if (!value.compareAndSetState(STATE_RESERVED, STATE_NOT_IN_USE)) + { + throw new IllegalStateException("Attempt to relinquish an object to the bag that was not reserved"); + } + + synchronizer.releaseShared(checkInTime); + } + + /** + * Our private synchronizer that handles notify/wait type semantics. + */ + private static class Synchronizer extends AbstractQueuedLongSynchronizer + { + private static final long serialVersionUID = 104753538004341218L; + + @Override + protected long tryAcquireShared(long startScanTime) + { + // fairness + if (hasQueuedPredecessors()) + { + return -1; + } + + return getState() > startScanTime ? 1 : -1; + } + + /** {@inheritDoc} */ + @Override + protected boolean tryReleaseShared(long updateTime) + { + setState(updateTime); + + return true; + } + } +} diff --git a/src/main/java/com/zaxxer/hikari/util/IBagManagable.java b/src/main/java/com/zaxxer/hikari/util/IBagManagable.java deleted file mode 100644 index 6a0c91f7b..000000000 --- a/src/main/java/com/zaxxer/hikari/util/IBagManagable.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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; - -/** - * - * @author Brett Wooldridge - */ -public interface IBagManagable -{ - int NOT_IN_USE = 0; - int IN_USE = 1; - int REMOVED = -1; - - void setState(int newState); - - int getState(); - - boolean compareAndSetState(int expectedState, int newState); -} diff --git a/src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java b/src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java deleted file mode 100644 index eebc83b9b..000000000 --- a/src/main/java/com/zaxxer/hikari/util/SpecializedConcurrentBag.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * 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; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.CopyOnWriteArraySet; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.AbstractQueuedLongSynchronizer; - -import static com.zaxxer.hikari.util.IBagManagable.NOT_IN_USE; -import static com.zaxxer.hikari.util.IBagManagable.IN_USE; -import static com.zaxxer.hikari.util.IBagManagable.REMOVED; - -/** - * This is a specialized concurrent bag that achieves superior performance - * to LinkedBlockingQueue and LinkedTransferQueue for the purposes of a - * connection pool. It uses ThreadLocal storage when possible to avoid - * locks, but resorts to scanning a common collection if there are no - * available connections in the ThreadLocal list. Idle connections in - * ThreadLocal lists can be "stolen" when the poll()ing thread has none - * of its own. It is a "lock-less" implementation using a specialized - * AbstractQueuedLongSynchronizer to manage cross-thread signaling. - * - * @author Brett Wooldridge - * - * @param the templated type to store in the bag - */ -public class SpecializedConcurrentBag -{ - private ThreadLocal> threadList; - private CopyOnWriteArraySet sharedList; - private Synchronizer synchronizer; - - /** - * Constructor. - * - * @param initialCapacity initial bag capacity - */ - public SpecializedConcurrentBag(int initialCapacity) - { - this.sharedList = new CopyOnWriteArraySet<>(); - this.synchronizer = new Synchronizer(); - this.threadList = new ThreadLocal>() { - protected LinkedList initialValue() - { - return new LinkedList<>(); - } - }; - } - - public T poll(long timeout, TimeUnit timeUnit) throws InterruptedException - { - // Try the thread-local list first - final LinkedList list = threadList.get(); - while (!list.isEmpty()) - { - final T reference = list.removeFirst(); - if (reference.compareAndSetState(NOT_IN_USE, IN_USE)) - { - return reference; - } - } - - // Otherwise, scan the shared list ... for maximum of timeout - timeout = timeUnit.toNanos(timeout); - do { - final long startScan = System.nanoTime(); - for (T reference : sharedList) - { - if (reference.compareAndSetState(NOT_IN_USE, IN_USE)) - { - return reference; - } - } - - synchronizer.tryAcquireSharedNanos(startScan, timeout); - - timeout -= (System.nanoTime() - startScan); - } while (timeout > 0); - - return null; - } - - public void add(T value) - { - sharedList.add(value); - synchronizer.releaseShared(1); - } - - public boolean offer(T value) - { - final long offerTime = System.nanoTime(); - if (value.compareAndSetState(IN_USE, NOT_IN_USE)) - { - threadList.get().addLast(value); - } - else - { - return false; - } - - synchronizer.releaseShared(offerTime); - - return true; - } - - public void remove(T value) - { - value.setState(REMOVED); - sharedList.remove(value); - } - - public List values(int state) - { - ArrayList list = new ArrayList<>(sharedList.size()); - for (T reference : sharedList) - { - if (reference.getState() == state) - { - list.add(reference); - } - } - - return list; - } - - public T checkout(T value) - { - if (value.compareAndSetState(NOT_IN_USE, IN_USE)) - { - return value; - } - - return null; - } - - public void checkin(T value) - { - final long checkInTime = System.nanoTime(); - value.compareAndSetState(IN_USE, NOT_IN_USE); - synchronizer.releaseShared(checkInTime); - } - - /** - * Our private synchronizer that handles notify/wait type semantics. - */ - private static class Synchronizer extends AbstractQueuedLongSynchronizer - { - private static final long serialVersionUID = 104753538004341218L; - - @Override - protected long tryAcquireShared(long startScanTime) - { - // fairness - if (hasQueuedPredecessors()) - { - return -1; - } - - return getState() > startScanTime ? 1 : -1; - } - - /** {@inheritDoc} */ - @Override - protected boolean tryReleaseShared(long updateTime) - { - setState(updateTime); - - return true; - } - } -}