Skip to content

Commit 9df73f7

Browse files
authored
Merge pull request #204 from emaccaull/fix/pool-lock-contention
Speed up concurrent pool creation
2 parents ed96951 + cccf87b commit 9df73f7

File tree

3 files changed

+91
-22
lines changed

3 files changed

+91
-22
lines changed

src/main/java/org/mariadb/jdbc/pool/Pool.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -534,8 +534,10 @@ private void registerJmx() throws Exception {
534534
String jmxName = poolTag.replace(":", "_");
535535
ObjectName name = new ObjectName("org.mariadb.jdbc.pool:type=" + jmxName);
536536

537-
if (!mbs.isRegistered(name)) {
538-
mbs.registerMBean(this, name);
537+
synchronized (mbs) {
538+
if (!mbs.isRegistered(name)) {
539+
mbs.registerMBean(this, name);
540+
}
539541
}
540542
}
541543

@@ -544,8 +546,10 @@ private void unRegisterJmx() throws Exception {
544546
String jmxName = poolTag.replace(":", "_");
545547
ObjectName name = new ObjectName("org.mariadb.jdbc.pool:type=" + jmxName);
546548

547-
if (mbs.isRegistered(name)) {
548-
mbs.unregisterMBean(name);
549+
synchronized (mbs) {
550+
if (mbs.isRegistered(name)) {
551+
mbs.unregisterMBean(name);
552+
}
549553
}
550554
}
551555

src/main/java/org/mariadb/jdbc/pool/Pools.java

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,53 @@
1414
public final class Pools {
1515

1616
private static final AtomicInteger poolIndex = new AtomicInteger();
17-
private static final Map<Configuration, Pool> poolMap = new ConcurrentHashMap<>();
17+
private static final Map<Configuration, PoolHolder> poolMap = new ConcurrentHashMap<>();
1818
private static ScheduledThreadPoolExecutor poolExecutor = null;
1919

20+
static class PoolHolder {
21+
private final Configuration conf;
22+
private final int poolIndex;
23+
private final ScheduledThreadPoolExecutor executor;
24+
private Pool pool;
25+
26+
PoolHolder(Configuration conf, int poolIndex, ScheduledThreadPoolExecutor executor) {
27+
this.conf = conf;
28+
this.poolIndex = poolIndex;
29+
this.executor = executor;
30+
}
31+
32+
synchronized Pool getPool() {
33+
if (pool == null) {
34+
pool = new Pool(conf, poolIndex, executor);
35+
}
36+
return pool;
37+
}
38+
}
39+
2040
/**
2141
* Get existing pool for a configuration. Create it if it doesn't exist.
2242
*
2343
* @param conf configuration parser
2444
* @return pool
2545
*/
2646
public static Pool retrievePool(Configuration conf) {
27-
if (!poolMap.containsKey(conf)) {
47+
PoolHolder holder = poolMap.get(conf);
48+
if (holder == null) {
2849
synchronized (poolMap) {
29-
if (!poolMap.containsKey(conf)) {
50+
holder = poolMap.get(conf);
51+
if (holder == null) {
3052
if (poolExecutor == null) {
3153
poolExecutor =
3254
new ScheduledThreadPoolExecutor(
3355
1, new PoolThreadFactory("MariaDbPool-maxTimeoutIdle-checker"));
3456
}
35-
Pool pool = new Pool(conf, poolIndex.incrementAndGet(), poolExecutor);
36-
poolMap.put(conf, pool);
37-
return pool;
57+
holder = new PoolHolder(conf, poolIndex.incrementAndGet(), poolExecutor);
58+
poolMap.put(conf, holder);
3859
}
3960
}
4061
}
41-
return poolMap.get(conf);
62+
// Don't initialize a pool while holding a lock on `poolMap`.
63+
return holder.getPool();
4264
}
4365

4466
/**
@@ -49,12 +71,9 @@ public static Pool retrievePool(Configuration conf) {
4971
public static void remove(Pool pool) {
5072
if (poolMap.containsKey(pool.getConf())) {
5173
synchronized (poolMap) {
52-
if (poolMap.containsKey(pool.getConf())) {
53-
poolMap.remove(pool.getConf());
54-
55-
if (poolMap.isEmpty()) {
56-
shutdownExecutor();
57-
}
74+
PoolHolder previous = poolMap.remove(pool.getConf());
75+
if (previous != null && poolMap.isEmpty()) {
76+
shutdownExecutor();
5877
}
5978
}
6079
}
@@ -63,9 +82,9 @@ public static void remove(Pool pool) {
6382
/** Close all pools. */
6483
public static void close() {
6584
synchronized (poolMap) {
66-
for (Pool pool : poolMap.values()) {
85+
for (PoolHolder holder : poolMap.values()) {
6786
try {
68-
pool.close();
87+
holder.getPool().close();
6988
} catch (Exception exception) {
7089
// eat
7190
}
@@ -85,10 +104,12 @@ public static void close(String poolName) {
85104
return;
86105
}
87106
synchronized (poolMap) {
88-
for (Pool pool : poolMap.values()) {
89-
if (poolName.equals(pool.getConf().poolName())) {
107+
for (PoolHolder holder : poolMap.values()) {
108+
if (poolName.equals(holder.conf.poolName())) {
90109
try {
91-
pool.close(); // Pool.close() calls Pools.remove(), which does the rest of the cleanup
110+
holder
111+
.getPool()
112+
.close(); // Pool.close() calls Pools.remove(), which does the rest of the cleanup
92113
} catch (Exception exception) {
93114
// eat
94115
}

src/test/java/org/mariadb/jdbc/integration/PoolDataSourceTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,18 @@
88
import java.lang.management.ManagementFactory;
99
import java.sql.*;
1010
import java.util.HashSet;
11+
import java.util.List;
1112
import java.util.Locale;
1213
import java.util.Set;
14+
import java.util.concurrent.CountDownLatch;
15+
import java.util.concurrent.ExecutorService;
16+
import java.util.concurrent.Executors;
17+
import java.util.concurrent.Future;
1318
import java.util.concurrent.LinkedBlockingQueue;
1419
import java.util.concurrent.ThreadPoolExecutor;
1520
import java.util.concurrent.TimeUnit;
21+
import java.util.stream.Collectors;
22+
import java.util.stream.IntStream;
1623
import javax.management.MBeanInfo;
1724
import javax.management.MBeanServer;
1825
import javax.management.ObjectName;
@@ -23,6 +30,7 @@
2330
import org.junit.jupiter.api.Assumptions;
2431
import org.junit.jupiter.api.BeforeAll;
2532
import org.junit.jupiter.api.Test;
33+
import org.junit.jupiter.api.Timeout;
2634
import org.mariadb.jdbc.MariaDbPoolDataSource;
2735
import org.mariadb.jdbc.pool.PoolThreadFactory;
2836
import org.mariadb.jdbc.pool.Pools;
@@ -774,4 +782,40 @@ public void ensureConnectionClose() throws Exception {
774782
assertFalse(xac.getConnection().isClosed());
775783
xac.close();
776784
}
785+
786+
@Timeout(value = 5, unit = TimeUnit.SECONDS)
787+
@Test
788+
public void testConcurrentCreationForDifferentHosts() throws Exception {
789+
CountDownLatch ready = new CountDownLatch(5);
790+
CountDownLatch start = new CountDownLatch(1);
791+
ExecutorService executor = Executors.newCachedThreadPool();
792+
try {
793+
// When many pools are created concurrently
794+
List<Future<MariaDbPoolDataSource>> futures =
795+
IntStream.rangeClosed(1, 5)
796+
.mapToObj(
797+
hostIndex ->
798+
executor.submit(
799+
() -> {
800+
ready.countDown();
801+
start.await();
802+
MariaDbPoolDataSource ds = new MariaDbPoolDataSource();
803+
ds.setUrl(
804+
"jdbc:mariadb://myhost" + hostIndex + ":5500/db?someOption=val");
805+
return ds;
806+
}))
807+
.collect(Collectors.toList());
808+
809+
ready.await();
810+
start.countDown();
811+
812+
// Then they should all be created in a timely manner
813+
for (Future<MariaDbPoolDataSource> future : futures) {
814+
future.get().close();
815+
}
816+
817+
} finally {
818+
executor.shutdown();
819+
}
820+
}
777821
}

0 commit comments

Comments
 (0)