diff options
author | Apollon Oikonomopoulos <apoikos@debian.org> | 2017-09-13 00:26:37 +0300 |
---|---|---|
committer | Apollon Oikonomopoulos <apoikos@debian.org> | 2017-09-13 00:26:37 +0300 |
commit | 808d040ea9d760bf468621984a3f2de865d35e7c (patch) | |
tree | 520db44f3458bcb02a60baacff82408fcfb49840 /src/test/java/com | |
parent | bd7b6679cea5620446718911de7a6764f81a9a7a (diff) |
New upstream version 2.7.1
Diffstat (limited to 'src/test/java/com')
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; } /* |