You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by kl...@apache.org on 2019/02/04 19:13:39 UTC
[geode] branch develop updated: GEODE-6301: Use ThreadInfo.toString
in ExecutorServiceRule.dumpThreads
This is an automated email from the ASF dual-hosted git repository.
klund pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git
The following commit(s) were added to refs/heads/develop by this push:
new 1daef02 GEODE-6301: Use ThreadInfo.toString in ExecutorServiceRule.dumpThreads
1daef02 is described below
commit 1daef02d413e15ecac6c0c3f08dc31ab7e7b6a24
Author: Kirk Lund <kl...@apache.org>
AuthorDate: Wed Jan 23 13:15:13 2019 -0800
GEODE-6301: Use ThreadInfo.toString in ExecutorServiceRule.dumpThreads
Cleanup javadocs and warnings in all ExecutorServiceRule tests.
---
.../rules/ExecutorServiceRuleIntegrationTest.java | 8 +-
.../test/junit/rules/ExecutorServiceRule.java | 42 +--
.../rules/ExecutorServiceRuleDumpThreadsTest.java | 401 +++++++++++++++++++++
.../rules/ExecutorServiceRuleGetThreadsTest.java | 1 -
.../test/junit/rules/ExecutorServiceRuleTest.java | 8 +-
5 files changed, 431 insertions(+), 29 deletions(-)
diff --git a/geode-junit/src/integrationTest/java/org/apache/geode/test/junit/rules/ExecutorServiceRuleIntegrationTest.java b/geode-junit/src/integrationTest/java/org/apache/geode/test/junit/rules/ExecutorServiceRuleIntegrationTest.java
index 15f3c31..0402cd7 100644
--- a/geode-junit/src/integrationTest/java/org/apache/geode/test/junit/rules/ExecutorServiceRuleIntegrationTest.java
+++ b/geode-junit/src/integrationTest/java/org/apache/geode/test/junit/rules/ExecutorServiceRuleIntegrationTest.java
@@ -32,6 +32,10 @@ import org.mockito.InOrder;
import org.apache.geode.test.junit.runners.TestRunner;
+/**
+ * Integration tests for {@link ExecutorServiceRule}. These are tests that pause a little longer
+ * than acceptable for a unit test.
+ */
public class ExecutorServiceRuleIntegrationTest {
private static volatile CountDownLatch hangLatch;
@@ -69,8 +73,7 @@ public class ExecutorServiceRuleIntegrationTest {
assertThat(result.wasSuccessful()).isTrue();
assertThat(isTestHung()).isTrue();
- await()
- .untilAsserted(() -> assertThat(executorService.isTerminated()).isTrue());
+ await().untilAsserted(() -> assertThat(executorService.isTerminated()).isTrue());
invocations.afterRule();
InOrder invocationOrder = inOrder(invocations);
@@ -117,6 +120,7 @@ public class ExecutorServiceRuleIntegrationTest {
}
interface Invocations {
+
void afterHangLatch();
void afterTerminateLatch();
diff --git a/geode-junit/src/main/java/org/apache/geode/test/junit/rules/ExecutorServiceRule.java b/geode-junit/src/main/java/org/apache/geode/test/junit/rules/ExecutorServiceRule.java
index d152bdf..ebce40f 100644
--- a/geode-junit/src/main/java/org/apache/geode/test/junit/rules/ExecutorServiceRule.java
+++ b/geode-junit/src/main/java/org/apache/geode/test/junit/rules/ExecutorServiceRule.java
@@ -272,28 +272,25 @@ public class ExecutorServiceRule extends SerializableExternalResource {
}
/**
- * Returns formatted call stacks of the {@code Thread}s that are directly in the
- * {@code ExecutorService}'s {@code ThreadGroup} excluding subgroups.
+ * Returns thread dumps for the {@code Thread}s that are in the {@code ExecutorService}'s
+ * {@code ThreadGroup} excluding subgroups.
*/
public String dumpThreads() {
- StringBuilder dump = new StringBuilder();
+ StringBuilder dumpWriter = new StringBuilder();
+
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
- ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(getThreadIds(), 100);
+ ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(getThreadIds(), true, true);
+
for (ThreadInfo threadInfo : threadInfos) {
- dump.append('"');
- dump.append(threadInfo.getThreadName());
- dump.append("\" ");
- final Thread.State state = threadInfo.getThreadState();
- dump.append("\n java.lang.Thread.State: ");
- dump.append(state);
- final StackTraceElement[] stackTraceElements = threadInfo.getStackTrace();
- for (final StackTraceElement stackTraceElement : stackTraceElements) {
- dump.append("\n at ");
- dump.append(stackTraceElement);
+ if (threadInfo == null) {
+ // sometimes ThreadMXBean.getThreadInfo returns array with one or more null elements
+ continue;
}
- dump.append("\n\n");
+ // ThreadInfo toString includes monitor and synchronizer details
+ dumpWriter.append(threadInfo);
}
- return dump.toString();
+
+ return dumpWriter.toString();
}
/**
@@ -301,16 +298,18 @@ public class ExecutorServiceRule extends SerializableExternalResource {
* a {@code Set<WeakReference<Thread>>} to track the {@code Thread}s in the factory's
* {@code ThreadGroup} excluding subgroups.
*/
- static class DedicatedThreadFactory implements ThreadFactory {
- private static final AtomicInteger poolNumber = new AtomicInteger(1);
+ protected static class DedicatedThreadFactory implements ThreadFactory {
+
+ private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1);
+
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
private final Set<WeakReference<Thread>> directThreads = new HashSet<>();
- DedicatedThreadFactory() {
+ protected DedicatedThreadFactory() {
group = new ThreadGroup(ExecutorServiceRule.class.getSimpleName() + "-ThreadGroup");
- namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
+ namePrefix = "pool-" + POOL_NUMBER.getAndIncrement() + "-thread-";
}
@Override
@@ -326,7 +325,7 @@ public class ExecutorServiceRule extends SerializableExternalResource {
return t;
}
- Set<Thread> getThreads() {
+ protected Set<Thread> getThreads() {
Set<Thread> value = new HashSet<>();
for (WeakReference<Thread> reference : directThreads) {
Thread thread = reference.get();
@@ -375,7 +374,6 @@ public class ExecutorServiceRule extends SerializableExternalResource {
/**
* Enables invocation of {@code shutdownNow} during {@code tearDown}. Default is enabled.
- *
*/
public Builder useShutdownNow() {
useShutdown = false;
diff --git a/geode-junit/src/test/java/org/apache/geode/test/junit/rules/ExecutorServiceRuleDumpThreadsTest.java b/geode-junit/src/test/java/org/apache/geode/test/junit/rules/ExecutorServiceRuleDumpThreadsTest.java
new file mode 100644
index 0000000..0fe1209
--- /dev/null
+++ b/geode-junit/src/test/java/org/apache/geode/test/junit/rules/ExecutorServiceRuleDumpThreadsTest.java
@@ -0,0 +1,401 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.test.junit.rules;
+
+import static java.lang.Integer.toHexString;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.apache.geode.test.awaitility.GeodeAwaitility.await;
+import static org.apache.geode.test.awaitility.GeodeAwaitility.getTimeout;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link ExecutorServiceRule#dumpThreads()}. If these tests become too brittle, then
+ * just delete the tests with names starting with "shows".
+ */
+public class ExecutorServiceRuleDumpThreadsTest {
+
+ private static final long TIMEOUT_MILLIS = getTimeout().getValueInMS();
+
+ private final CountDownLatch terminateLatch = new CountDownLatch(1);
+
+ @Rule
+ public ExecutorServiceRule executorServiceRule = new ExecutorServiceRule();
+
+ @After
+ public void tearDown() {
+ terminateLatch.countDown();
+ }
+
+ @Test
+ public void includesThreadName() throws InterruptedException {
+ CountDownLatch threadRunning = new CountDownLatch(1);
+
+ executorServiceRule.submit(() -> {
+ threadRunning.countDown();
+ terminateLatch.await(TIMEOUT_MILLIS, MILLISECONDS);
+ return null;
+ });
+
+ threadRunning.await(TIMEOUT_MILLIS, MILLISECONDS);
+
+ Set<Thread> threads = executorServiceRule.getThreads();
+ assertThat(threads).hasSize(1);
+
+ Iterator<Thread> threadIterator = threads.iterator();
+ String threadName = threadIterator.next().getName();
+ String dump = executorServiceRule.dumpThreads();
+
+ assertThat(dump)
+ .contains(threadName);
+ }
+
+ @Test
+ public void includesThreadNamesForMultipleThreads() throws InterruptedException {
+ CountDownLatch threadRunning = new CountDownLatch(2);
+
+ for (int i = 0; i < 2; i++) {
+ executorServiceRule.submit(() -> {
+ threadRunning.countDown();
+ terminateLatch.await(TIMEOUT_MILLIS, MILLISECONDS);
+ return null;
+ });
+ }
+
+ threadRunning.await(TIMEOUT_MILLIS, MILLISECONDS);
+
+ Set<Thread> threads = executorServiceRule.getThreads();
+ assertThat(threads).hasSize(2);
+
+ Iterator<Thread> threadIterator = threads.iterator();
+ String threadName1 = threadIterator.next().getName();
+ String threadName2 = threadIterator.next().getName();
+ String dump = executorServiceRule.dumpThreads();
+
+ assertThat(dump)
+ .contains(threadName1)
+ .contains(threadName2);
+ }
+
+ @Test
+ public void includesThreadId() throws InterruptedException {
+ CountDownLatch threadRunning = new CountDownLatch(1);
+
+ executorServiceRule.submit(() -> {
+ threadRunning.countDown();
+ terminateLatch.await(TIMEOUT_MILLIS, MILLISECONDS);
+ return null;
+ });
+
+ threadRunning.await(TIMEOUT_MILLIS, MILLISECONDS);
+
+ Set<Thread> threads = executorServiceRule.getThreads();
+ assertThat(threads).hasSize(1);
+
+ Iterator<Thread> threadIterator = threads.iterator();
+ String threadId = String.valueOf(threadIterator.next().getId());
+ String dump = executorServiceRule.dumpThreads();
+
+ assertThat(dump)
+ .contains(threadId);
+ }
+
+ @Test
+ public void includesThreadIdsForMultipleThreads() throws InterruptedException {
+ CountDownLatch threadRunning = new CountDownLatch(2);
+
+ for (int i = 0; i < 2; i++) {
+ executorServiceRule.submit(() -> {
+ threadRunning.countDown();
+ terminateLatch.await(TIMEOUT_MILLIS, MILLISECONDS);
+ return null;
+ });
+ }
+
+ threadRunning.await(TIMEOUT_MILLIS, MILLISECONDS);
+
+ Set<Thread> threads = executorServiceRule.getThreads();
+ assertThat(threads).hasSize(2);
+
+ Iterator<Thread> threadIterator = threads.iterator();
+ String threadId1 = String.valueOf(threadIterator.next().getId());
+ String threadId2 = String.valueOf(threadIterator.next().getId());
+ String dump = executorServiceRule.dumpThreads();
+
+ assertThat(dump)
+ .contains(threadId1)
+ .contains(threadId2);
+ }
+
+ @Test
+ public void includesLockedMonitors() throws InterruptedException {
+ Object lock1 = new Object();
+ Object lock2 = new Object();
+ CountDownLatch lockedMonitors = new CountDownLatch(1);
+
+ executorServiceRule.submit(() -> {
+ synchronized (lock1) {
+ synchronized (lock2) {
+ lockedMonitors.countDown();
+ terminateLatch.await(TIMEOUT_MILLIS, MILLISECONDS);
+ }
+ }
+ return null;
+ });
+
+ lockedMonitors.await(TIMEOUT_MILLIS, MILLISECONDS);
+
+ String dump = executorServiceRule.dumpThreads();
+
+ assertThat(dump)
+ .contains("- locked " + lock1.getClass().getName() + "@" + toHexString(lock1.hashCode()))
+ .contains("- locked " + lock2.getClass().getName() + "@" + toHexString(lock2.hashCode()));
+ }
+
+ @Test
+ public void includesLockedSynchronizers() throws InterruptedException {
+ Lock sync = new ReentrantLock();
+ CountDownLatch lockedSynchronizer = new CountDownLatch(1);
+
+ executorServiceRule.submit(() -> {
+ sync.lockInterruptibly();
+ try {
+ lockedSynchronizer.countDown();
+ terminateLatch.await(TIMEOUT_MILLIS, MILLISECONDS);
+ } finally {
+ sync.unlock();
+ }
+ return null;
+ });
+
+ lockedSynchronizer.await(TIMEOUT_MILLIS, MILLISECONDS);
+
+ String dump = executorServiceRule.dumpThreads();
+
+ assertThat(dump)
+ .contains("Number of locked synchronizers = 2")
+ .contains(sync.getClass().getName() + "$NonfairSync@");
+ }
+
+ @Test
+ public void showsThreadAwaitingLatch() {
+ executorServiceRule.submit(() -> terminateLatch.await(TIMEOUT_MILLIS, MILLISECONDS));
+
+ await().untilAsserted(() -> {
+ String dump = executorServiceRule.dumpThreads();
+
+ assertThat(dump)
+ .contains("TIMED_WAITING on " + terminateLatch.getClass().getName() + "$Sync@")
+ .contains("waiting on " + terminateLatch.getClass().getName() + "$Sync@");
+ });
+ }
+
+ @Test
+ public void showsThreadsInMonitorDeadlock() throws InterruptedException {
+ Object lock1 = new Object();
+ Object lock2 = new Object();
+ CountDownLatch deadlockLatch = new CountDownLatch(1);
+ CountDownLatch acquiredLock1Latch = new CountDownLatch(1);
+ CountDownLatch acquiredLock2Latch = new CountDownLatch(1);
+
+ executorServiceRule.submit(() -> {
+ synchronized (lock1) {
+ acquiredLock1Latch.countDown();
+ deadlockLatch.await(TIMEOUT_MILLIS, MILLISECONDS);
+ synchronized (lock2) {
+ System.out.println(Thread.currentThread().getName() + " acquired lock1 and lock2");
+ }
+ }
+ return null;
+ });
+
+ executorServiceRule.submit(() -> {
+ synchronized (lock2) {
+ acquiredLock2Latch.countDown();
+ deadlockLatch.await(TIMEOUT_MILLIS, MILLISECONDS);
+ synchronized (lock1) {
+ System.out.println(Thread.currentThread().getName() + " acquired lock2 and lock1");
+ }
+ }
+ return null;
+ });
+
+ acquiredLock1Latch.await(TIMEOUT_MILLIS, MILLISECONDS);
+ acquiredLock2Latch.await(TIMEOUT_MILLIS, MILLISECONDS);
+
+ deadlockLatch.countDown();
+
+ await().untilAsserted(() -> {
+ String dump = executorServiceRule.dumpThreads();
+
+ String lock1Hash = toHexString(lock1.hashCode());
+ String lock2Hash = toHexString(lock2.hashCode());
+
+ assertThat(dump)
+ .contains("BLOCKED on " + lock1.getClass().getName() + "@" + lock1Hash + " owned by ")
+ .contains("BLOCKED on " + lock2.getClass().getName() + "@" + lock2Hash + " owned by ")
+
+ .contains("- blocked on " + lock1.getClass().getName() + "@" + lock1Hash)
+ .contains("- blocked on " + lock2.getClass().getName() + "@" + lock2Hash)
+
+ .contains("- locked " + lock1.getClass().getName() + "@" + lock1Hash)
+ .contains("- locked " + lock2.getClass().getName() + "@" + lock2Hash);
+ });
+ }
+
+ @Test
+ public void showsThreadsInSynchronizerDeadlock() throws InterruptedException {
+ Lock sync1 = new ReentrantLock();
+ Lock sync2 = new ReentrantLock();
+ CountDownLatch deadlockLatch = new CountDownLatch(1);
+ CountDownLatch acquiredSync1Latch = new CountDownLatch(1);
+ CountDownLatch acquiredSync2Latch = new CountDownLatch(1);
+
+ executorServiceRule.submit(() -> {
+ sync1.lockInterruptibly();
+ try {
+ acquiredSync1Latch.countDown();
+ deadlockLatch.await(TIMEOUT_MILLIS, MILLISECONDS);
+ sync2.lockInterruptibly();
+ sync2.unlock();
+ } finally {
+ sync1.unlock();
+ }
+ return null;
+ });
+
+ executorServiceRule.submit(() -> {
+ sync2.lockInterruptibly();
+ try {
+ acquiredSync2Latch.countDown();
+ deadlockLatch.await(TIMEOUT_MILLIS, MILLISECONDS);
+ sync1.lockInterruptibly();
+ sync1.unlock();
+ } finally {
+ sync2.unlock();
+ }
+ return null;
+ });
+
+ acquiredSync1Latch.await(TIMEOUT_MILLIS, MILLISECONDS);
+ acquiredSync2Latch.await(TIMEOUT_MILLIS, MILLISECONDS);
+
+ deadlockLatch.countDown();
+
+ await().untilAsserted(() -> {
+ String dump = executorServiceRule.dumpThreads();
+
+ Set<Thread> threads = executorServiceRule.getThreads();
+ assertThat(threads).hasSize(2);
+
+ Iterator<Thread> threadIterator = threads.iterator();
+ String[] threadNames = new String[2];
+ for (int i = 0; i < threadNames.length; i++) {
+ threadNames[i] = threadIterator.next().getName();
+ }
+
+ String syncType = ReentrantLock.class.getName() + "$NonfairSync@";
+
+ assertThat(dump)
+ .contains("WAITING on " + syncType)
+ .contains("owned by \"" + threadNames[0] + "\"")
+ .contains("owned by \"" + threadNames[1] + "\"")
+ .contains("waiting on " + syncType)
+ .contains("Number of locked synchronizers = 2")
+ .contains("- " + syncType);
+ });
+ }
+
+ @Test
+ public void showsThreadBlockedInWait() {
+ Object object = new Object();
+ AtomicBoolean waiting = new AtomicBoolean(true);
+ AtomicReference<Thread> threadRef = new AtomicReference<>();
+
+ executorServiceRule.submit(() -> {
+ threadRef.set(Thread.currentThread());
+ while (waiting.get()) {
+ synchronized (object) {
+ object.wait();
+ }
+ }
+ return null;
+ });
+
+ await().untilAsserted(() -> {
+ String dump = executorServiceRule.dumpThreads();
+
+ waiting.set(false);
+ synchronized (object) {
+ object.notifyAll();
+ }
+
+ String objectHashCode = toHexString(object.hashCode());
+
+ assertThat(dump)
+ .contains("- waiting on " + Object.class.getName() + "@" + objectHashCode);
+ });
+ }
+
+ @Test
+ public void showsThreadBlockedByOtherThread() {
+ Object object = new Object();
+ CountDownLatch pausingLatch = new CountDownLatch(1);
+ AtomicBoolean waiting = new AtomicBoolean(true);
+
+ executorServiceRule.submit(() -> {
+ synchronized (object) {
+ pausingLatch.await(TIMEOUT_MILLIS, MILLISECONDS);
+ }
+ return null;
+ });
+
+ executorServiceRule.submit(() -> {
+ while (waiting.get()) {
+ synchronized (object) {
+ object.wait();
+ }
+ }
+ return null;
+ });
+
+ await().untilAsserted(() -> {
+ String dump = executorServiceRule.dumpThreads();
+
+ pausingLatch.countDown();
+ synchronized (object) {
+ object.notifyAll();
+ }
+ waiting.set(false);
+
+ String objectHashCode = toHexString(object.hashCode());
+
+ assertThat(dump)
+ .contains("- locked " + Object.class.getName() + "@" + objectHashCode)
+ .contains("- blocked on " + Object.class.getName() + "@" + objectHashCode);
+ });
+ }
+}
diff --git a/geode-junit/src/test/java/org/apache/geode/test/junit/rules/ExecutorServiceRuleGetThreadsTest.java b/geode-junit/src/test/java/org/apache/geode/test/junit/rules/ExecutorServiceRuleGetThreadsTest.java
index 3e12a5e..fc4951b 100644
--- a/geode-junit/src/test/java/org/apache/geode/test/junit/rules/ExecutorServiceRuleGetThreadsTest.java
+++ b/geode-junit/src/test/java/org/apache/geode/test/junit/rules/ExecutorServiceRuleGetThreadsTest.java
@@ -89,7 +89,6 @@ public class ExecutorServiceRuleGetThreadsTest {
executorServiceRule.submit(() -> terminateLatch.await(TIMEOUT_MILLIS, MILLISECONDS));
long[] threadIds = executorServiceRule.getThreadIds();
-
assertThat(threadIds).hasSize(2);
Set<Thread> threads = executorServiceRule.getThreads();
diff --git a/geode-junit/src/test/java/org/apache/geode/test/junit/rules/ExecutorServiceRuleTest.java b/geode-junit/src/test/java/org/apache/geode/test/junit/rules/ExecutorServiceRuleTest.java
index 5c9273e..ce2481d 100644
--- a/geode-junit/src/test/java/org/apache/geode/test/junit/rules/ExecutorServiceRuleTest.java
+++ b/geode-junit/src/test/java/org/apache/geode/test/junit/rules/ExecutorServiceRuleTest.java
@@ -42,13 +42,13 @@ public class ExecutorServiceRuleTest {
private static volatile ExecutorService executorService;
@Before
- public void setUp() throws Exception {
+ public void setUp() {
hangLatch = new CountDownLatch(1);
terminateLatch = new CountDownLatch(1);
}
@After
- public void tearDown() throws Exception {
+ public void tearDown() {
while (hangLatch != null && hangLatch.getCount() > 0) {
hangLatch.countDown();;
}
@@ -133,7 +133,7 @@ public class ExecutorServiceRuleTest {
public static class HasExecutorService extends HasExecutorServiceRule {
@Test
- public void doTest() throws Exception {
+ public void doTest() {
// nothing
}
}
@@ -141,7 +141,7 @@ public class ExecutorServiceRuleTest {
public static class Hangs extends HasExecutorServiceRule {
@Test
- public void doTest() throws Exception {
+ public void doTest() {
executorServiceRule.runAsync(() -> {
try {
hangLatch.await();