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();