summaryrefslogtreecommitdiff
path: root/src/test/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/java')
-rwxr-xr-xsrc/test/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerTest.java39
-rw-r--r--src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java11
-rw-r--r--src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java44
-rw-r--r--src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java2
-rw-r--r--src/test/java/com/zaxxer/hikari/mocks/StubStatement.java11
-rw-r--r--src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java118
-rwxr-xr-xsrc/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java4
-rwxr-xr-xsrc/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java2
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java2
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/MetricsTrackerTest.java86
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/MiscTest.java7
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/SaturatedPoolTest830.java156
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java5
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestElf.java23
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestJNDI.java27
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestMBean.java93
-rw-r--r--src/test/java/com/zaxxer/hikari/pool/TestMetrics.java46
-rw-r--r--src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java112
18 files changed, 632 insertions, 156 deletions
diff --git a/src/test/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerTest.java b/src/test/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerTest.java
new file mode 100755
index 0000000..761ef12
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/metrics/micrometer/MicrometerMetricsTrackerTest.java
@@ -0,0 +1,39 @@
+package com.zaxxer.hikari.metrics.micrometer;
+
+import com.zaxxer.hikari.metrics.PoolStats;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MicrometerMetricsTrackerTest {
+
+ private MeterRegistry mockMeterRegistry = new SimpleMeterRegistry();
+
+ private MicrometerMetricsTracker testee;
+
+ @Before
+ public void setup(){
+ testee = new MicrometerMetricsTracker("mypool", new PoolStats(1000L) {
+ @Override
+ protected void update() {
+ // nothing
+ }
+ }, mockMeterRegistry);
+ }
+
+ @Test
+ public void close() throws Exception {
+ Assert.assertNotNull(mockMeterRegistry.find("Wait"));
+ Assert.assertNotNull(mockMeterRegistry.find("Usage"));
+ Assert.assertNotNull(mockMeterRegistry.find("ConnectionCreation"));
+ Assert.assertNotNull(mockMeterRegistry.find("ConnectionTimeoutRate"));
+ Assert.assertNotNull(mockMeterRegistry.find("TotalConnections"));
+ Assert.assertNotNull(mockMeterRegistry.find("IdleConnections"));
+ Assert.assertNotNull(mockMeterRegistry.find("ActiveConnections"));
+ Assert.assertNotNull(mockMeterRegistry.find("PendingConnections"));
+
+ testee.close();
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java b/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java
index 46b2e74..63746a3 100644
--- a/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java
+++ b/src/test/java/com/zaxxer/hikari/metrics/prometheus/HikariCPCollectorTest.java
@@ -60,10 +60,11 @@ public class HikariCPCollectorTest {
StubConnection.slowCreate = true;
try (HikariDataSource ds = new HikariDataSource(config)) {
- assertThat(getValue("hikaricp_active_connections", "HikariPool-1"), is(0.0));
- assertThat(getValue("hikaricp_idle_connections", "HikariPool-1"), is(0.0));
- assertThat(getValue("hikaricp_pending_threads", "HikariPool-1"), is(0.0));
- assertThat(getValue("hikaricp_connections", "HikariPool-1"), is(0.0));
+ String poolName = ds.getHikariConfigMXBean().getPoolName();
+ assertThat(getValue("hikaricp_active_connections", poolName), is(0.0));
+ assertThat(getValue("hikaricp_idle_connections", poolName), is(0.0));
+ assertThat(getValue("hikaricp_pending_threads", poolName), is(0.0));
+ assertThat(getValue("hikaricp_connections", poolName), is(0.0));
}
finally {
StubConnection.slowCreate = false;
@@ -105,7 +106,7 @@ public class HikariCPCollectorTest {
try (Connection connection1 = ds.getConnection()) {
// close immediately
}
-
+
assertThat(getValue("hikaricp_active_connections", "connectionClosed"), is(0.0));
assertThat(getValue("hikaricp_idle_connections", "connectionClosed"), is(1.0));
assertThat(getValue("hikaricp_pending_threads", "connectionClosed"), is(0.0));
diff --git a/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java b/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java
index a7b0b03..079cfb5 100644
--- a/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java
+++ b/src/test/java/com/zaxxer/hikari/metrics/prometheus/PrometheusMetricsTrackerTest.java
@@ -33,6 +33,7 @@ import com.zaxxer.hikari.HikariDataSource;
import io.prometheus.client.CollectorRegistry;
public class PrometheusMetricsTrackerTest {
+
@Test
public void recordConnectionTimeout() throws Exception {
HikariConfig config = newHikariConfig();
@@ -40,13 +41,13 @@ public class PrometheusMetricsTrackerTest {
config.setJdbcUrl("jdbc:h2:mem:");
config.setMaximumPoolSize(2);
config.setConnectionTimeout(250);
-
+
String[] labelNames = {"pool"};
String[] labelValues = {config.getPoolName()};
try (HikariDataSource hikariDataSource = new HikariDataSource(config)) {
try (Connection connection1 = hikariDataSource.getConnection();
- Connection connection2 = hikariDataSource.getConnection()) {
+ Connection connection2 = hikariDataSource.getConnection()) {
try (Connection connection3 = hikariDataSource.getConnection()) {
}
catch (SQLTransientConnectionException ignored) {
@@ -54,13 +55,13 @@ public class PrometheusMetricsTrackerTest {
}
assertThat(CollectorRegistry.defaultRegistry.getSampleValue(
- "hikaricp_connection_timeout_count",
+ "hikaricp_connection_timeout_total",
labelNames,
labelValues), is(1.0));
assertThat(CollectorRegistry.defaultRegistry.getSampleValue(
"hikaricp_connection_acquired_nanos_count",
labelNames,
- labelValues), is(equalTo(2.0)));
+ labelValues), is(equalTo(3.0)));
assertTrue(CollectorRegistry.defaultRegistry.getSampleValue(
"hikaricp_connection_acquired_nanos_sum",
labelNames,
@@ -75,4 +76,39 @@ public class PrometheusMetricsTrackerTest {
labelValues) > 0.0);
}
}
+
+ @Test
+ public void testMultiplePoolName() throws Exception {
+ String[] labelNames = {"pool"};
+
+ HikariConfig config = newHikariConfig();
+ config.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
+ config.setPoolName("first");
+ config.setJdbcUrl("jdbc:h2:mem:");
+ config.setMaximumPoolSize(2);
+ config.setConnectionTimeout(250);
+ String[] labelValues1 = {config.getPoolName()};
+
+ try (HikariDataSource ignored = new HikariDataSource(config)) {
+ assertThat(CollectorRegistry.defaultRegistry.getSampleValue(
+ "hikaricp_connection_timeout_total",
+ labelNames,
+ labelValues1), is(0.0));
+
+ HikariConfig config2 = newHikariConfig();
+ config2.setMetricsTrackerFactory(new PrometheusMetricsTrackerFactory());
+ config2.setPoolName("second");
+ config2.setJdbcUrl("jdbc:h2:mem:");
+ config2.setMaximumPoolSize(4);
+ config2.setConnectionTimeout(250);
+ String[] labelValues2 = {config2.getPoolName()};
+
+ try (HikariDataSource ignored2 = new HikariDataSource(config2)) {
+ assertThat(CollectorRegistry.defaultRegistry.getSampleValue(
+ "hikaricp_connection_timeout_total",
+ labelNames,
+ labelValues2), is(0.0));
+ }
+ }
+ }
}
diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java b/src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java
index 5c48f85..2092611 100644
--- a/src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java
+++ b/src/test/java/com/zaxxer/hikari/mocks/StubPreparedStatement.java
@@ -45,7 +45,7 @@ import java.util.Calendar;
*/
public class StubPreparedStatement extends StubStatement implements PreparedStatement
{
- public StubPreparedStatement(Connection connection)
+ StubPreparedStatement(Connection connection)
{
super(connection);
}
diff --git a/src/test/java/com/zaxxer/hikari/mocks/StubStatement.java b/src/test/java/com/zaxxer/hikari/mocks/StubStatement.java
index 73fec69..99e7b13 100644
--- a/src/test/java/com/zaxxer/hikari/mocks/StubStatement.java
+++ b/src/test/java/com/zaxxer/hikari/mocks/StubStatement.java
@@ -16,6 +16,8 @@
package com.zaxxer.hikari.mocks;
+import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
+
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -30,6 +32,8 @@ import java.sql.Statement;
public class StubStatement implements Statement
{
public static volatile boolean oldDriver;
+
+ private static volatile long simulatedQueryTime;
private boolean closed;
private Connection connection;
@@ -37,6 +41,10 @@ public class StubStatement implements Statement
this.connection = connection;
}
+ public static void setSimulatedQueryTime(long time) {
+ simulatedQueryTime = time;
+ }
+
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
@Override
@@ -168,6 +176,9 @@ public class StubStatement implements Statement
public boolean execute(String sql) throws SQLException
{
checkClosed();
+ if (simulatedQueryTime > 0) {
+ quietlySleep(simulatedQueryTime);
+ }
return false;
}
diff --git a/src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java b/src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java
index 761a802..a41b916 100644
--- a/src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java
+++ b/src/test/java/com/zaxxer/hikari/osgi/OSGiBundleTest.java
@@ -17,6 +17,10 @@ package com.zaxxer.hikari.osgi;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.model.InitializationError;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.PaxExam;
@@ -24,9 +28,9 @@ import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import javax.inject.Inject;
-
import java.io.File;
+import static com.zaxxer.hikari.pool.TestElf.isJava9;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.CoreOptions.*;
@@ -34,58 +38,76 @@ import static org.ops4j.pax.exam.CoreOptions.*;
/**
* @author lburgazzoli
*/
-@RunWith(PaxExam.class)
+@RunWith(OSGiBundleTest.ConditionalPaxExam.class)
public class OSGiBundleTest
{
- @Inject
- BundleContext context;
+ @Test
+ public void checkInject()
+ {
+ assertNotNull(context);
+ }
+
+ @Test
+ public void checkBundle()
+ {
+ Boolean bundleFound = false;
+ Boolean bundleActive = false;
- @Configuration
- public Option[] config()
- {
- return options(
- systemProperty("org.osgi.framework.storage.clean").value("true"),
- systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("WARN"),
- mavenBundle("org.slf4j","slf4j-api","1.7.5"),
- mavenBundle("org.slf4j","slf4j-simple","1.7.5").noStart(),
- mavenBundle("org.javassist", "javassist", "3.19.0-GA"),
- new File("target/classes").exists()
- ? bundle("reference:file:target/classes")
- : bundle("reference:file:../target/classes"),
- junitBundles(),
- cleanCaches()
- );
- }
+ Bundle[] bundles = context.getBundles();
+ for (Bundle bundle : bundles) {
+ if (bundle != null) {
+ if (bundle.getSymbolicName().equals("com.zaxxer.HikariCP")) {
+ bundleFound = true;
+ if (bundle.getState() == Bundle.ACTIVE) {
+ bundleActive = true;
+ }
+ }
+ }
+ }
- @Test
- public void checkInject()
- {
- assertNotNull(context);
- }
+ assertTrue(bundleFound);
+ assertTrue(bundleActive);
+ }
- @Test
- public void checkBundle()
- {
- Boolean bundleFound = false;
- Boolean bundleActive = false;
+ @Inject
+ BundleContext context;
- Bundle[] bundles = context.getBundles();
- for(Bundle bundle : bundles)
- {
- if(bundle != null)
- {
- if(bundle.getSymbolicName().equals("com.zaxxer.HikariCP"))
- {
- bundleFound = true;
- if(bundle.getState() == Bundle.ACTIVE)
- {
- bundleActive = true;
- }
- }
- }
- }
+ @Configuration
+ public Option[] config()
+ {
+ return options(
+ systemProperty("org.osgi.framework.storage.clean").value("true"),
+ systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("WARN"),
+ mavenBundle("org.slf4j", "slf4j-api", "1.7.5"),
+ mavenBundle("org.slf4j", "slf4j-simple", "1.7.5").noStart(),
+ new File("target/classes").exists()
+ ? bundle("reference:file:target/classes")
+ : bundle("reference:file:../target/classes"),
+ junitBundles(),
+ cleanCaches()
+ );
+ }
+
+ public static class ConditionalPaxExam extends PaxExam
+ {
+ public ConditionalPaxExam(Class<?> klass) throws InitializationError {
+ super(klass);
+ }
+
+ @Override
+ public void run(RunNotifier notifier) {
+ if (!isJava9()) {
+ super.run(notifier);
+ }
+ }
+
+ @Override
+ public void filter(Filter filter) throws NoTestsRemainException {
+ if (isJava9()) {
+ throw new NoTestsRemainException();
+ }
- assertTrue(bundleFound);
- assertTrue(bundleActive);
- }
+ super.filter(filter);
+ }
+ }
}
diff --git a/src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java b/src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java
index e66ba58..8585485 100755
--- a/src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java
+++ b/src/test/java/com/zaxxer/hikari/pool/ConnectionPoolSizeVsThreadsTest.java
@@ -45,9 +45,9 @@ import com.zaxxer.hikari.mocks.StubDataSource;
*/
public class ConnectionPoolSizeVsThreadsTest {
- public static final Logger LOGGER = LoggerFactory.getLogger(ConnectionPoolSizeVsThreadsTest.class);
+ private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionPoolSizeVsThreadsTest.class);
- public static final int ITERATIONS = 50_000;
+ private static final int ITERATIONS = 50_000;
@Test
public void testPoolSizeAboutSameSizeAsThreadCount() throws Exception {
diff --git a/src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java b/src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java
index 31f8363..c2bd000 100755
--- a/src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java
+++ b/src/test/java/com/zaxxer/hikari/pool/ConnectionRaceConditionTest.java
@@ -51,7 +51,7 @@ public class ConnectionRaceConditionTest
config.setMinimumIdle(0);
config.setMaximumPoolSize(10);
config.setInitializationFailTimeout(Long.MAX_VALUE);
- config.setConnectionTimeout(2500);
+ config.setConnectionTimeout(5000);
config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
setSlf4jLogLevel(ConcurrentBag.class, Level.INFO);
diff --git a/src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java b/src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java
index 1a7caa7..f62de43 100644
--- a/src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java
+++ b/src/test/java/com/zaxxer/hikari/pool/HouseKeeperCleanupTest.java
@@ -59,7 +59,7 @@ public class HouseKeeperCleanupTest
config.setInitializationFailTimeout(Long.MAX_VALUE);
config.setConnectionTimeout(2500);
config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
- config.setScheduledExecutorService(executor);
+ config.setScheduledExecutor(executor);
HikariConfig config2 = newHikariConfig();
config.copyState(config2);
diff --git a/src/test/java/com/zaxxer/hikari/pool/MetricsTrackerTest.java b/src/test/java/com/zaxxer/hikari/pool/MetricsTrackerTest.java
new file mode 100644
index 0000000..494fd4d
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/MetricsTrackerTest.java
@@ -0,0 +1,86 @@
+package com.zaxxer.hikari.pool;
+
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.metrics.IMetricsTracker;
+import com.zaxxer.hikari.mocks.StubDataSource;
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.SQLTransientConnectionException;
+import java.util.concurrent.TimeUnit;
+
+import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource;
+import static junit.framework.TestCase.fail;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author wvuong@chariotsolutions.com on 2/16/17.
+ */
+public class MetricsTrackerTest
+{
+
+ @Test(expected = SQLTransientConnectionException.class)
+ public void connectionTimeoutIsRecorded() throws Exception
+ {
+ int timeoutMillis = 1000;
+ int timeToCreateNewConnectionMillis = timeoutMillis * 2;
+
+ StubDataSource stubDataSource = new StubDataSource();
+ stubDataSource.setConnectionAcquistionTime(timeToCreateNewConnectionMillis);
+
+ StubMetricsTracker metricsTracker = new StubMetricsTracker();
+
+ try (HikariDataSource ds = newHikariDataSource()) {
+ ds.setMinimumIdle(0);
+ ds.setMaximumPoolSize(1);
+ ds.setConnectionTimeout(timeoutMillis);
+ ds.setDataSource(stubDataSource);
+ ds.setMetricsTrackerFactory((poolName, poolStats) -> metricsTracker);
+
+ try (Connection c = ds.getConnection()) {
+ fail("Connection shouldn't have been successfully created due to configured connection timeout");
+
+ } finally {
+ // assert that connection timeout was measured
+ assertThat(metricsTracker.connectionTimeoutRecorded, is(true));
+ // assert that measured time to acquire connection should be roughly equal or greater than the configured connection timeout time
+ assertTrue(metricsTracker.connectionAcquiredNanos >= TimeUnit.NANOSECONDS.convert(timeoutMillis, TimeUnit.MILLISECONDS));
+ }
+ }
+ }
+
+ private static class StubMetricsTracker implements IMetricsTracker
+ {
+
+ private Long connectionCreatedMillis;
+ private Long connectionAcquiredNanos;
+ private Long connectionBorrowedMillis;
+ private boolean connectionTimeoutRecorded;
+
+ @Override
+ public void recordConnectionCreatedMillis(long connectionCreatedMillis)
+ {
+ this.connectionCreatedMillis = connectionCreatedMillis;
+ }
+
+ @Override
+ public void recordConnectionAcquiredNanos(long elapsedAcquiredNanos)
+ {
+ this.connectionAcquiredNanos = elapsedAcquiredNanos;
+ }
+
+ @Override
+ public void recordConnectionUsageMillis(long elapsedBorrowedMillis)
+ {
+ this.connectionBorrowedMillis = elapsedBorrowedMillis;
+ }
+
+ @Override
+ public void recordConnectionTimeout()
+ {
+ this.connectionTimeoutRecorded = true;
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/MiscTest.java b/src/test/java/com/zaxxer/hikari/pool/MiscTest.java
index f9698ef..2791f3c 100644
--- a/src/test/java/com/zaxxer/hikari/pool/MiscTest.java
+++ b/src/test/java/com/zaxxer/hikari/pool/MiscTest.java
@@ -102,7 +102,7 @@ public class MiscTest
try (PrintStream ps = new PrintStream(baos, true)) {
setSlf4jTargetStream(Class.forName("com.zaxxer.hikari.pool.ProxyLeakTask"), ps);
setConfigUnitTest(true);
-
+
HikariConfig config = newHikariConfig();
config.setMinimumIdle(0);
config.setMaximumPoolSize(4);
@@ -110,11 +110,11 @@ public class MiscTest
config.setMetricRegistry(null);
config.setLeakDetectionThreshold(TimeUnit.SECONDS.toMillis(1));
config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
+
try (HikariDataSource ds = new HikariDataSource(config)) {
setSlf4jLogLevel(HikariPool.class, Level.DEBUG);
getPool(ds).logPoolState();
-
+
try (Connection connection = ds.getConnection()) {
quietlySleep(SECONDS.toMillis(4));
connection.close();
@@ -128,6 +128,7 @@ public class MiscTest
finally
{
setConfigUnitTest(false);
+ setSlf4jLogLevel(HikariPool.class, Level.INFO);
}
}
}
diff --git a/src/test/java/com/zaxxer/hikari/pool/SaturatedPoolTest830.java b/src/test/java/com/zaxxer/hikari/pool/SaturatedPoolTest830.java
new file mode 100644
index 0000000..db8f970
--- /dev/null
+++ b/src/test/java/com/zaxxer/hikari/pool/SaturatedPoolTest830.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2017 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.pool;
+
+import static com.zaxxer.hikari.pool.TestElf.getConcurrentBag;
+import static com.zaxxer.hikari.pool.TestElf.getPool;
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.setSlf4jLogLevel;
+import static com.zaxxer.hikari.util.ClockSource.currentTime;
+import static com.zaxxer.hikari.util.ClockSource.elapsedMillis;
+import static com.zaxxer.hikari.util.UtilityElf.quietlySleep;
+import static java.lang.Math.round;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Arrays;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.logging.log4j.Level;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.mocks.StubConnection;
+import com.zaxxer.hikari.mocks.StubStatement;
+
+/**
+ * @author Brett Wooldridge
+ */
+public class SaturatedPoolTest830
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(SaturatedPoolTest830.class);
+ private static final int MAX_POOL_SIZE = 10;
+
+ @Test
+ public void saturatedPoolTest() throws Exception {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(5);
+ config.setMaximumPoolSize(MAX_POOL_SIZE);
+ config.setInitializationFailTimeout(Long.MAX_VALUE);
+ config.setConnectionTimeout(1000);
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ StubConnection.slowCreate = true;
+ StubStatement.setSimulatedQueryTime(1000);
+ setSlf4jLogLevel(HikariPool.class, Level.DEBUG);
+ System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "5000");
+
+ final long start = currentTime();
+
+ try (final HikariDataSource ds = new HikariDataSource(config)) {
+ LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
+ ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 50 /*core*/, 50 /*max*/, 2 /*keepalive*/, SECONDS, queue, new ThreadPoolExecutor.CallerRunsPolicy());
+ threadPool.allowCoreThreadTimeOut(true);
+
+ AtomicInteger windowIndex = new AtomicInteger();
+ boolean[] failureWindow = new boolean[100];
+ Arrays.fill(failureWindow, true);
+
+ // Initial saturation
+ for (int i = 0; i < 50; i++) {
+ threadPool.execute(() -> {
+ try (Connection conn = ds.getConnection();
+ Statement stmt = conn.createStatement()) {
+ stmt.execute("SELECT bogus FROM imaginary");
+ }
+ catch (SQLException e) {
+ LOGGER.info(e.getMessage());
+ }
+ });
+ }
+
+ long sleep = 80;
+outer: while (true) {
+ quietlySleep(sleep);
+
+ if (elapsedMillis(start) > SECONDS.toMillis(12) && sleep < 100) {
+ sleep = 100;
+ LOGGER.warn("Switching to 100ms sleep");
+ }
+ else if (elapsedMillis(start) > SECONDS.toMillis(6) && sleep < 90) {
+ sleep = 90;
+ LOGGER.warn("Switching to 90ms sleep");
+ }
+
+ threadPool.execute(() -> {
+ int ndx = windowIndex.incrementAndGet() % failureWindow.length;
+
+ try (Connection conn = ds.getConnection();
+ Statement stmt = conn.createStatement()) {
+ stmt.execute("SELECT bogus FROM imaginary");
+ failureWindow[ndx] = false;
+ }
+ catch (SQLException e) {
+ LOGGER.info(e.getMessage());
+ failureWindow[ndx] = true;
+ }
+ });
+
+ for (int i = 0; i < failureWindow.length; i++) {
+ if (failureWindow[i]) {
+ if (elapsedMillis(start) % (SECONDS.toMillis(1) - sleep) < sleep) {
+ LOGGER.info("Active threads {}, submissions per second {}, waiting threads {}",
+ threadPool.getActiveCount(),
+ SECONDS.toMillis(1) / sleep,
+ getPool(ds).getThreadsAwaitingConnection());
+ }
+ continue outer;
+ }
+ }
+
+ LOGGER.info("Timeouts have subsided.");
+ LOGGER.info("Active threads {}, submissions per second {}, waiting threads {}",
+ threadPool.getActiveCount(),
+ SECONDS.toMillis(1) / sleep,
+ getPool(ds).getThreadsAwaitingConnection());
+ break;
+ }
+
+ LOGGER.info("Waiting for completion of {} active tasks.", threadPool.getActiveCount());
+ while (getPool(ds).getActiveConnections() > 0) {
+ quietlySleep(50);
+ }
+
+ assertEquals("Rate not in balance at 10req/s", SECONDS.toMillis(1) / sleep, 10L);
+ }
+ finally {
+ StubStatement.setSimulatedQueryTime(0);
+ StubConnection.slowCreate = false;
+ System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs");
+ setSlf4jLogLevel(HikariPool.class, Level.INFO);
+ }
+ }
+}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java b/src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java
index 15f8974..2760543 100644
--- a/src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java
+++ b/src/test/java/com/zaxxer/hikari/pool/TestConnectionTimeoutRetry.java
@@ -251,9 +251,9 @@ public class TestConnectionTimeoutRetry
Connection connection5 = ds.getConnection();
Connection connection6 = ds.getConnection();
Connection connection7 = ds.getConnection()) {
-
+
sleep(1300);
-
+
assertSame("Total connections not as expected", 10, pool.getTotalConnections());
assertSame("Idle connections not as expected", 3, pool.getIdleConnections());
}
@@ -273,5 +273,6 @@ public class TestConnectionTimeoutRetry
public void after()
{
System.getProperties().remove("com.zaxxer.hikari.housekeeping.periodMs");
+ setSlf4jLogLevel(HikariPool.class, Level.INFO);
}
}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestElf.java b/src/test/java/com/zaxxer/hikari/pool/TestElf.java
index 6438b11..7ec2925 100644
--- a/src/test/java/com/zaxxer/hikari/pool/TestElf.java
+++ b/src/test/java/com/zaxxer/hikari/pool/TestElf.java
@@ -19,7 +19,6 @@ package com.zaxxer.hikari.pool;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.sql.Connection;
-import java.util.HashMap;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Appender;
@@ -32,6 +31,7 @@ import org.slf4j.LoggerFactory;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.util.ConcurrentBag;
/**
* Utility methods for testing.
@@ -44,6 +44,10 @@ public final class TestElf
// default constructor
}
+ public static boolean isJava9() {
+ return System.getProperty("java.version").startsWith("9");
+ }
+
public static HikariPool getPool(HikariDataSource ds)
{
try {
@@ -56,20 +60,19 @@ public final class TestElf
}
}
- @SuppressWarnings("unchecked")
- public static HashMap<Object, HikariPool> getMultiPool(HikariDataSource ds)
+ static ConcurrentBag<?> getConcurrentBag(HikariDataSource ds)
{
try {
- Field field = ds.getClass().getDeclaredField("multiPool");
+ Field field = HikariPool.class.getDeclaredField("connectionBag");
field.setAccessible(true);
- return (HashMap<Object, HikariPool>) field.get(ds);
+ return (ConcurrentBag<?>) field.get(getPool(ds));
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
- public static boolean getConnectionCommitDirtyState(Connection connection)
+ static boolean getConnectionCommitDirtyState(Connection connection)
{
try {
Field field = ProxyConnection.class.getDeclaredField("isCommitStateDirty");
@@ -81,7 +84,7 @@ public final class TestElf
}
}
- public static void setConfigUnitTest(boolean unitTest)
+ static void setConfigUnitTest(boolean unitTest)
{
try {
Field field = HikariConfig.class.getDeclaredField("unitTest");
@@ -93,7 +96,7 @@ public final class TestElf
}
}
- public static void setSlf4jTargetStream(Class<?> clazz, PrintStream stream)
+ static void setSlf4jTargetStream(Class<?> clazz, PrintStream stream)
{
try {
Log4jLogger log4Jlogger = (Log4jLogger) LoggerFactory.getLogger(clazz);
@@ -114,7 +117,7 @@ public final class TestElf
}
}
- public static void setSlf4jLogLevel(Class<?> clazz, Level logLevel)
+ static void setSlf4jLogLevel(Class<?> clazz, Level logLevel)
{
try {
Log4jLogger log4Jlogger = (Log4jLogger) LoggerFactory.getLogger(clazz);
@@ -144,7 +147,7 @@ public final class TestElf
return config;
}
- public static HikariDataSource newHikariDataSource()
+ static HikariDataSource newHikariDataSource()
{
final StackTraceElement callerStackTrace = Thread.currentThread().getStackTrace()[2];
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestJNDI.java b/src/test/java/com/zaxxer/hikari/pool/TestJNDI.java
index b5cd40c..6e041cb 100644
--- a/src/test/java/com/zaxxer/hikari/pool/TestJNDI.java
+++ b/src/test/java/com/zaxxer/hikari/pool/TestJNDI.java
@@ -15,17 +15,21 @@
*/
package com.zaxxer.hikari.pool;
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static com.zaxxer.hikari.pool.TestElf.newHikariDataSource;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import javax.naming.Context;
+import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.RefAddr;
import javax.naming.Reference;
+import com.zaxxer.hikari.HikariConfig;
import org.junit.Test;
import org.osjava.sj.jndi.AbstractContext;
@@ -33,6 +37,8 @@ import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariJNDIFactory;
import com.zaxxer.hikari.mocks.StubDataSource;
+import java.sql.Connection;
+
public class TestJNDI
{
@Test
@@ -94,6 +100,27 @@ public class TestJNDI
}
}
+ @Test
+ public void testJndiLookup4() throws Exception
+ {
+ System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.osjava.sj.memory.MemoryContextFactory");
+ System.setProperty("org.osjava.sj.jndi.shared", "true");
+ InitialContext ic = new InitialContext();
+
+ StubDataSource ds = new StubDataSource();
+
+ Context subcontext = ic.createSubcontext("java:/comp/env/jdbc");
+ subcontext.bind("java:/comp/env/jdbc/myDS", ds);
+
+ HikariConfig config = newHikariConfig();
+ config.setDataSourceJNDI("java:/comp/env/jdbc/myDS");
+
+ try (HikariDataSource hds = new HikariDataSource(config);
+ Connection conn = hds.getConnection()) {
+ assertNotNull(conn);
+ }
+ }
+
private class BogusContext extends AbstractContext
{
@Override
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestMBean.java b/src/test/java/com/zaxxer/hikari/pool/TestMBean.java
index 55ba733..a77c960 100644
--- a/src/test/java/com/zaxxer/hikari/pool/TestMBean.java
+++ b/src/test/java/com/zaxxer/hikari/pool/TestMBean.java
@@ -15,30 +15,83 @@
*/
package com.zaxxer.hikari.pool;
-import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.HikariPoolMXBean;
+import org.junit.Test;
+import javax.management.JMX;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import java.lang.management.ManagementFactory;
+import java.sql.Connection;
import java.sql.SQLException;
+import java.util.concurrent.TimeUnit;
-import org.junit.Test;
-
-import com.zaxxer.hikari.HikariConfig;
-import com.zaxxer.hikari.HikariDataSource;
+import static com.zaxxer.hikari.pool.TestElf.newHikariConfig;
+import static org.junit.Assert.assertEquals;
public class TestMBean
{
- @Test
- public void testMBeanRegistration() throws SQLException
- {
- HikariConfig config = newHikariConfig();
- config.setMinimumIdle(0);
- config.setMaximumPoolSize(1);
- config.setRegisterMbeans(true);
- config.setConnectionTimeout(2800);
- config.setConnectionTestQuery("VALUES 1");
- config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
- try (HikariDataSource ds = new HikariDataSource(config)) {
- // Close immediately
- }
- }
+ @Test
+ public void testMBeanRegistration() throws SQLException {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(0);
+ config.setMaximumPoolSize(1);
+ config.setRegisterMbeans(true);
+ config.setConnectionTimeout(2800);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+ // Close immediately
+ }
+ }
+
+ @Test
+ public void testMBeanReporting() throws SQLException, InterruptedException, MalformedObjectNameException {
+ HikariConfig config = newHikariConfig();
+ config.setMinimumIdle(3);
+ config.setMaximumPoolSize(5);
+ config.setRegisterMbeans(true);
+ config.setConnectionTimeout(2800);
+ config.setConnectionTestQuery("VALUES 1");
+ config.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
+
+ System.setProperty("com.zaxxer.hikari.housekeeping.periodMs", "100");
+
+ try (HikariDataSource ds = new HikariDataSource(config)) {
+
+ ds.setIdleTimeout(3000);
+
+ TimeUnit.SECONDS.sleep(1);
+
+ MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
+ ObjectName poolName = new ObjectName("com.zaxxer.hikari:type=Pool (testMBeanReporting)");
+ HikariPoolMXBean hikariPoolMXBean = JMX.newMXBeanProxy(mBeanServer, poolName, HikariPoolMXBean.class);
+
+ assertEquals(0, hikariPoolMXBean.getActiveConnections());
+ assertEquals(3, hikariPoolMXBean.getIdleConnections());
+
+ try (Connection connection = ds.getConnection()) {
+ assertEquals(1, hikariPoolMXBean.getActiveConnections());
+
+ TimeUnit.SECONDS.sleep(1);
+
+ assertEquals(3, hikariPoolMXBean.getIdleConnections());
+ assertEquals(4, hikariPoolMXBean.getTotalConnections());
+ }
+
+ TimeUnit.SECONDS.sleep(2);
+
+ assertEquals(0, hikariPoolMXBean.getActiveConnections());
+ assertEquals(3, hikariPoolMXBean.getIdleConnections());
+ assertEquals(3, hikariPoolMXBean.getTotalConnections());
+
+ }
+ finally {
+ System.clearProperty("com.zaxxer.hikari.housekeeping.periodMs");
+ }
+ }
}
diff --git a/src/test/java/com/zaxxer/hikari/pool/TestMetrics.java b/src/test/java/com/zaxxer/hikari/pool/TestMetrics.java
index fcba58e..775bb49 100644
--- a/src/test/java/com/zaxxer/hikari/pool/TestMetrics.java
+++ b/src/test/java/com/zaxxer/hikari/pool/TestMetrics.java
@@ -44,8 +44,6 @@ import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
import com.zaxxer.hikari.metrics.dropwizard.CodahaleMetricsTrackerFactory;
import com.zaxxer.hikari.util.UtilityElf;
-import shaded.org.codehaus.plexus.interpolation.os.Os;
-
/**
* Test HikariCP/CodaHale metrics integration.
*
@@ -85,7 +83,7 @@ public class TestMetrics
@Test
public void testMetricUsage() throws SQLException
{
- assumeFalse(Os.isFamily(Os.FAMILY_WINDOWS));
+ assumeFalse(System.getProperty("os.name").contains("Windows"));
MetricRegistry metricRegistry = new MetricRegistry();
HikariConfig config = newHikariConfig();
@@ -156,19 +154,19 @@ public class TestMetrics
try (HikariDataSource ds = newHikariDataSource()) {
ds.setMaximumPoolSize(1);
ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
+
MetricRegistry metricRegistry = new MetricRegistry();
HealthCheckRegistry healthRegistry = new HealthCheckRegistry();
-
+
try {
try (Connection connection = ds.getConnection()) {
// close immediately
}
-
+
// After the pool as started, we can only set them once...
ds.setMetricRegistry(metricRegistry);
ds.setHealthCheckRegistry(healthRegistry);
-
+
// and never again...
ds.setMetricRegistry(metricRegistry);
fail("Should not have been allowed to set registry after pool started");
@@ -192,19 +190,19 @@ public class TestMetrics
try (HikariDataSource ds = newHikariDataSource()) {
ds.setMaximumPoolSize(1);
ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
+
MetricRegistry metricRegistry = new MetricRegistry();
HealthCheckRegistry healthRegistry = new HealthCheckRegistry();
-
+
ds.setMetricRegistry(metricRegistry);
ds.setHealthCheckRegistry(healthRegistry);
-
+
// before the pool is started, we can set it any number of times...
ds.setMetricRegistry(metricRegistry);
ds.setHealthCheckRegistry(healthRegistry);
-
+
try (Connection connection = ds.getConnection()) {
-
+
// after the pool is started, we cannot set it any more
ds.setMetricRegistry(metricRegistry);
fail("Should not have been allowed to set registry after pool started");
@@ -221,15 +219,15 @@ public class TestMetrics
try (HikariDataSource ds = newHikariDataSource()) {
ds.setMaximumPoolSize(1);
ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
+
MetricRegistry metricRegistry = new MetricRegistry();
MetricsTrackerFactory metricsTrackerFactory = new CodahaleMetricsTrackerFactory(metricRegistry);
-
+
try (Connection connection = ds.getConnection()) {
-
+
// After the pool as started, we can only set them once...
ds.setMetricsTrackerFactory(metricsTrackerFactory);
-
+
// and never again...
ds.setMetricsTrackerFactory(metricsTrackerFactory);
fail("Should not have been allowed to set metricsTrackerFactory after pool started");
@@ -254,16 +252,16 @@ public class TestMetrics
try (HikariDataSource ds = newHikariDataSource()) {
ds.setMaximumPoolSize(1);
ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
+
MetricRegistry metricRegistry = new MetricRegistry();
-
+
// before the pool is started, we can set it any number of times using either setter
ds.setMetricRegistry(metricRegistry);
ds.setMetricRegistry(metricRegistry);
ds.setMetricRegistry(metricRegistry);
-
+
try (Connection connection = ds.getConnection()) {
-
+
// after the pool is started, we cannot set it any more
ds.setMetricRegistry(metricRegistry);
fail("Should not have been allowed to set registry after pool started");
@@ -280,17 +278,17 @@ public class TestMetrics
try (HikariDataSource ds = newHikariDataSource()) {
ds.setMaximumPoolSize(1);
ds.setDataSourceClassName("com.zaxxer.hikari.mocks.StubDataSource");
-
+
MetricRegistry metricRegistry = new MetricRegistry();
MetricsTrackerFactory metricsTrackerFactory = new CodahaleMetricsTrackerFactory(metricRegistry);
-
+
// before the pool is started, we can set it any number of times using either setter
ds.setMetricsTrackerFactory(metricsTrackerFactory);
ds.setMetricsTrackerFactory(metricsTrackerFactory);
ds.setMetricsTrackerFactory(metricsTrackerFactory);
-
+
try (Connection connection = ds.getConnection()) {
-
+
// after the pool is started, we cannot set it any more
ds.setMetricsTrackerFactory(metricsTrackerFactory);
fail("Should not have been allowed to set registry factory after pool started");
diff --git a/src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java b/src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java
index 06957b2..1c25a8f 100644
--- a/src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java
+++ b/src/test/java/com/zaxxer/hikari/util/TomcatConcurrentBagLeakTest.java
@@ -16,9 +16,12 @@
package com.zaxxer.hikari.util;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
+import com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.DataInputStream;
import java.io.IOException;
@@ -26,15 +29,16 @@ import java.lang.ref.Reference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
import java.util.concurrent.CompletableFuture;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.zaxxer.hikari.util.ConcurrentBag.IConcurrentBagEntry;
+import static com.zaxxer.hikari.pool.TestElf.isJava9;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeTrue;
/**
* @author Brett Wooldridge
@@ -45,6 +49,8 @@ public class TomcatConcurrentBagLeakTest
@Test
public void testConcurrentBagForLeaks() throws Exception
{
+ assumeTrue(!isJava9());
+
ClassLoader cl = new FauxWebClassLoader();
Class<?> clazz = cl.loadClass(this.getClass().getName() + "$FauxWebContext");
Object fauxWebContext = clazz.newInstance();
@@ -60,6 +66,8 @@ public class TomcatConcurrentBagLeakTest
@Test
public void testConcurrentBagForLeaks2() throws Exception
{
+ assumeTrue(!isJava9());
+
ClassLoader cl = this.getClass().getClassLoader();
Class<?> clazz = cl.loadClass(this.getClass().getName() + "$FauxWebContext");
Object fauxWebContext = clazz.newInstance();
@@ -127,12 +135,15 @@ public class TomcatConcurrentBagLeakTest
}
}
+ @SuppressWarnings("unused")
public static class FauxWebContext
{
private static final Logger log = LoggerFactory.getLogger(FauxWebContext.class);
+ @SuppressWarnings("WeakerAccess")
public Exception failureException;
+ @SuppressWarnings("unused")
public void createConcurrentBag() throws InterruptedException
{
try (ConcurrentBag<PoolEntry> bag = new ConcurrentBag<>((x) -> CompletableFuture.completedFuture(Boolean.TRUE))) {
@@ -169,19 +180,19 @@ public class TomcatConcurrentBagLeakTest
Method expungeStaleEntriesMethod = tlmClass.getDeclaredMethod("expungeStaleEntries");
expungeStaleEntriesMethod.setAccessible(true);
- for (int i = 0; i < threads.length; i++) {
+ for (Thread thread : threads) {
Object threadLocalMap;
- if (threads[i] != null) {
+ if (thread != null) {
// Clear the first map
- threadLocalMap = threadLocalsField.get(threads[i]);
+ threadLocalMap = threadLocalsField.get(thread);
if (null != threadLocalMap) {
expungeStaleEntriesMethod.invoke(threadLocalMap);
checkThreadLocalMapForLeaks(threadLocalMap, tableField);
}
// Clear the second map
- threadLocalMap = inheritableThreadLocalsField.get(threads[i]);
+ threadLocalMap = inheritableThreadLocalsField.get(thread);
if (null != threadLocalMap) {
expungeStaleEntriesMethod.invoke(threadLocalMap);
checkThreadLocalMapForLeaks(threadLocalMap, tableField);
@@ -190,7 +201,7 @@ public class TomcatConcurrentBagLeakTest
}
}
catch (Throwable t) {
- log.warn("Failed to check for ThreadLocal references for web application [{}]", t);
+ log.warn("Failed to check for ThreadLocal references for web application [{}]", getContextName(), t);
failureException = new Exception();
}
}
@@ -212,8 +223,7 @@ public class TomcatConcurrentBagLeakTest
if (map != null) {
Object[] table = (Object[]) internalTableField.get(map);
if (table != null) {
- for (int j = 0; j < table.length; j++) {
- Object obj = table[j];
+ for (Object obj : table) {
if (obj != null) {
boolean keyLoadedByWebapp = false;
boolean valueLoadedByWebapp = false;
@@ -236,8 +246,7 @@ public class TomcatConcurrentBagLeakTest
args[1] = getPrettyClassName(key.getClass());
try {
args[2] = key.toString();
- }
- catch (Exception e) {
+ } catch (Exception e) {
log.warn("Unable to determine string representation of key of type [{}]", args[1], e);
args[2] = "Unknown";
}
@@ -246,8 +255,7 @@ public class TomcatConcurrentBagLeakTest
args[3] = getPrettyClassName(value.getClass());
try {
args[4] = value.toString();
- }
- catch (Exception e) {
+ } catch (Exception e) {
log.warn("webappClassLoader.checkThreadLocalsForLeaks.badValue {}", args[3], e);
args[4] = "Unknown";
}
@@ -255,21 +263,19 @@ public class TomcatConcurrentBagLeakTest
if (valueLoadedByWebapp) {
log.error("The web application [{}] created a ThreadLocal with key " +
- "(value [{}]) and a value of type [{}] (value [{}]) but failed to remove " +
- "it when the web application was stopped. Threads are going to be renewed " +
- "over time to try and avoid a probable memory leak.", args);
+ "(value [{}]) and a value of type [{}] (value [{}]) but failed to remove " +
+ "it when the web application was stopped. Threads are going to be renewed " +
+ "over time to try and avoid a probable memory leak.", args);
failureException = new Exception();
- }
- else if (value == null) {
+ } else if (value == null) {
log.debug("The web application [{}] created a ThreadLocal with key of type [{}] " +
- "(value [{}]). The ThreadLocal has been correctly set to null and the " +
- "key will be removed by GC.", args);
+ "(value [{}]). The ThreadLocal has been correctly set to null and the " +
+ "key will be removed by GC.", args);
failureException = new Exception();
- }
- else {
+ } else {
log.debug("The web application [{}] created a ThreadLocal with key of type [{}] " +
- "(value [{}]) and a value of type [{}] (value [{}]). Since keys are only " +
- "weakly held by the ThreadLocal Map this is not a memory leak.", args);
+ "(value [{}]) and a value of type [{}] (value [{}]). Since keys are only " +
+ "weakly held by the ThreadLocal Map this is not a memory leak.", args);
failureException = new Exception();
}
}
@@ -279,9 +285,45 @@ public class TomcatConcurrentBagLeakTest
}
}
- private boolean loadedByThisOrChild(Object key)
- {
- return key.getClass().getClassLoader() == this.getClass().getClassLoader();
+ /**
+ * @param o object to test, may be null
+ * @return <code>true</code> if o has been loaded by the current classloader
+ * or one of its descendants.
+ */
+ private boolean loadedByThisOrChild(Object o) {
+ if (o == null) {
+ return false;
+ }
+
+ Class<?> clazz;
+ if (o instanceof Class) {
+ clazz = (Class<?>) o;
+ } else {
+ clazz = o.getClass();
+ }
+
+ ClassLoader cl = clazz.getClassLoader();
+ while (cl != null) {
+ if (cl == this.getClass().getClassLoader()) {
+ return true;
+ }
+ cl = cl.getParent();
+ }
+
+ if (o instanceof Collection<?>) {
+ Iterator<?> iter = ((Collection<?>) o).iterator();
+ try {
+ while (iter.hasNext()) {
+ Object entry = iter.next();
+ if (loadedByThisOrChild(entry)) {
+ return true;
+ }
+ }
+ } catch (ConcurrentModificationException e) {
+ log.warn("Failed to check for ThreadLocal references for web application [{}]", getContextName(), e);
+ }
+ }
+ return false;
}
/*