< prev index next >

test/jdk/com/sun/management/HotSpotDiagnosticMXBean/DumpThreads.java

Print this page
*** 1,7 ***
  /*
!  * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as
   * published by the Free Software Foundation.
--- 1,7 ---
  /*
!  * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as
   * published by the Free Software Foundation.

*** 36,38 ***
  
  import java.lang.management.ManagementFactory;
  import java.nio.file.Files;
  import java.nio.file.FileAlreadyExistsException;
  import java.nio.file.Path;
- import java.util.Objects;
  import java.time.ZonedDateTime;
  import java.util.concurrent.CountDownLatch;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.ForkJoinPool;
  import java.util.concurrent.locks.LockSupport;
  import java.util.stream.Stream;
  import com.sun.management.HotSpotDiagnosticMXBean;
  import com.sun.management.HotSpotDiagnosticMXBean.ThreadDumpFormat;
  import jdk.test.lib.threaddump.ThreadDump;
  
  import org.junit.jupiter.api.Test;
  import org.junit.jupiter.api.BeforeAll;
  import org.junit.jupiter.params.ParameterizedTest;
  import org.junit.jupiter.params.provider.MethodSource;
  import static org.junit.jupiter.api.Assertions.*;
  
  class DumpThreads {
      private static boolean trackAllThreads;
  
      @BeforeAll
      static void setup() throws Exception {
          String s = System.getProperty("jdk.trackAllThreads");
          trackAllThreads = (s == null) || s.isEmpty() || Boolean.parseBoolean(s);
      }
  
      /**
       * ExecutorService implementations that have their object identity in the container
       * name so they can be found in the JSON format.
       */
      static Stream<ExecutorService> executors() {
--- 36,89 ---
  
  import java.lang.management.ManagementFactory;
  import java.nio.file.Files;
  import java.nio.file.FileAlreadyExistsException;
  import java.nio.file.Path;
  import java.time.ZonedDateTime;
+ import java.util.List;
+ import java.util.Objects;
+ import java.util.Set;
  import java.util.concurrent.CountDownLatch;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.ForkJoinPool;
+ import java.util.concurrent.atomic.AtomicReference;
  import java.util.concurrent.locks.LockSupport;
+ import java.util.concurrent.locks.ReentrantLock;
+ import java.util.stream.Collectors;
  import java.util.stream.Stream;
  import com.sun.management.HotSpotDiagnosticMXBean;
  import com.sun.management.HotSpotDiagnosticMXBean.ThreadDumpFormat;
  import jdk.test.lib.threaddump.ThreadDump;
  
  import org.junit.jupiter.api.Test;
  import org.junit.jupiter.api.BeforeAll;
  import org.junit.jupiter.params.ParameterizedTest;
  import org.junit.jupiter.params.provider.MethodSource;
  import static org.junit.jupiter.api.Assertions.*;
+ import static org.junit.jupiter.api.Assumptions.*;
  
  class DumpThreads {
      private static boolean trackAllThreads;
  
      @BeforeAll
      static void setup() throws Exception {
          String s = System.getProperty("jdk.trackAllThreads");
          trackAllThreads = (s == null) || s.isEmpty() || Boolean.parseBoolean(s);
      }
  
+     /**
+      * Test thread dump in plain text format.
+      */
+     @Test
+     void testPlainText() throws Exception {
+         List<String> lines = dumpThreadsToPlainText();
+ 
+         // pid should be on the first line
+         String pid = Long.toString(ProcessHandle.current().pid());
+         assertEquals(pid, lines.get(0));
+ 
+         // timestamp should be on the second line
+         String secondLine = lines.get(1);
+         ZonedDateTime.parse(secondLine);
+ 
+         // runtime version should be on third line
+         String vs = Runtime.version().toString();
+         assertEquals(vs, lines.get(2));
+ 
+         // dump should include current thread
+         Thread currentThread = Thread.currentThread();
+         if (trackAllThreads || !currentThread.isVirtual()) {
+             ThreadFields fields = findThread(currentThread.threadId(), lines);
+             assertNotNull(fields, "current thread not found");
+             assertEquals(currentThread.getName(), fields.name());
+         }
+     }
+ 
+     /**
+      * Test thread dump in JSON format.
+      */
+     @Test
+     void testJsonFormat() throws Exception {
+         ThreadDump threadDump = dumpThreadsToJson();
+ 
+         // dump should include current thread in the root container
+         Thread currentThread = Thread.currentThread();
+         if (trackAllThreads || !currentThread.isVirtual()) {
+             ThreadDump.ThreadInfo ti = threadDump.rootThreadContainer()
+                     .findThread(currentThread.threadId())
+                     .orElse(null);
+             assertNotNull(ti, "current thread not found");
+         }
+     }
+ 
      /**
       * ExecutorService implementations that have their object identity in the container
       * name so they can be found in the JSON format.
       */
      static Stream<ExecutorService> executors() {

*** 76,177 ***
                  Executors.newVirtualThreadPerTaskExecutor()
          );
      }
  
      /**
!      * Test thread dump in plain text format contains information about the current
-      * thread and a virtual thread created directly with the Thread API.
       */
!     @Test
!     void testRootContainerPlainTextFormat() throws Exception {
!         Thread vthread = Thread.ofVirtual().start(LockSupport::park);
!         try {
!             testDumpThreadsPlainText(vthread, trackAllThreads);
-         } finally {
-             LockSupport.unpark(vthread);
          }
      }
  
      /**
!      * Test thread dump in JSON format contains information about the current
-      * thread and a virtual thread created directly with the Thread API.
       */
      @Test
!     void testRootContainerJsonFormat() throws Exception {
!         Thread vthread = Thread.ofVirtual().start(LockSupport::park);
-         try {
-             testDumpThreadsJson(null, vthread, trackAllThreads);
-         } finally {
-             LockSupport.unpark(vthread);
-         }
      }
  
      /**
!      * Test thread dump in plain text format includes a thread executing a task in the
-      * given ExecutorService.
       */
!     @ParameterizedTest
!     @MethodSource("executors")
!     void testExecutorServicePlainTextFormat(ExecutorService executor) throws Exception {
!         try (executor) {
!             Thread thread = forkParker(executor);
!             try {
!                 testDumpThreadsPlainText(thread, true);
!             } finally {
!                 LockSupport.unpark(thread);
!             }
          }
      }
  
      /**
!      * Test thread dump in JSON format includes a thread executing a task in the
-      * given ExecutorService.
       */
!     @ParameterizedTest
!     @MethodSource("executors")
!     void testExecutorServiceJsonFormat(ExecutorService executor) throws Exception {
!         try (executor) {
!             Thread thread = forkParker(executor);
!             try {
!                 testDumpThreadsJson(Objects.toIdentityString(executor), thread, true);
!             } finally {
!                 LockSupport.unpark(thread);
              }
          }
      }
  
      /**
!      * Test thread dump in JSON format includes a thread executing a task in the
-      * fork-join common pool.
       */
      @Test
!     void testForkJoinPool() throws Exception {
!         ForkJoinPool pool = ForkJoinPool.commonPool();
!         Thread thread = forkParker(pool);
          try {
!             testDumpThreadsJson("ForkJoinPool.commonPool", thread, true);
          } finally {
!             LockSupport.unpark(thread);
          }
      }
  
      /**
!      * Invoke HotSpotDiagnosticMXBean.dumpThreads to create a thread dump in plain text
-      * format, then sanity check that the thread dump includes expected strings, the
-      * current thread, and maybe the given thread.
-      * @param thread the thread to test if included
-      * @param expectInDump true if the thread is expected to be included
       */
!     private void testDumpThreadsPlainText(Thread thread, boolean expectInDump) throws Exception {
!         Path file = genOutputPath(".txt");
!         var mbean = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class);
!         mbean.dumpThreads(file.toString(), ThreadDumpFormat.TEXT_PLAIN);
!         System.err.format("Dumped to %s%n", file);
! 
!         // pid should be on the first line
!         String line1 = line(file, 0);
!         String pid = Long.toString(ProcessHandle.current().pid());
!         assertTrue(line1.contains(pid));
! 
-         // timestamp should be on the second line
-         String line2 = line(file, 1);
-         ZonedDateTime.parse(line2);
- 
-         // runtime version should be on third line
-         String line3 = line(file, 2);
-         String vs = Runtime.version().toString();
-         assertTrue(line3.contains(vs));
  
!         // test if thread is included in thread dump
-         assertEquals(expectInDump, isPresent(file, thread));
  
!         // current thread should be included if platform thread or tracking all threads
!         Thread currentThread = Thread.currentThread();
!         boolean currentThreadExpected = trackAllThreads || !currentThread.isVirtual();
!         assertEquals(currentThreadExpected, isPresent(file, currentThread));
      }
  
      /**
!      * Invoke HotSpotDiagnosticMXBean.dumpThreads to create a thread dump in JSON format.
-      * The thread dump is parsed as a JSON object and checked to ensure that it contains
-      * expected data, the current thread, and maybe the given thread.
-      * @param containerName the name of the container or null for the root container
-      * @param thread the thread to test if included
-      * @param expect true if the thread is expected to be included
       */
!     private void testDumpThreadsJson(String containerName,
!                                      Thread thread,
!                                      boolean expectInDump) throws Exception {
!         Path file = genOutputPath(".json");
!         var mbean = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class);
!         mbean.dumpThreads(file.toString(), ThreadDumpFormat.JSON);
!         System.err.format("Dumped to %s%n", file);
  
!         // parse the JSON text
!         String jsonText = Files.readString(file);
-         ThreadDump threadDump = ThreadDump.parse(jsonText);
- 
-         // test threadDump/processId
-         assertTrue(threadDump.processId() == ProcessHandle.current().pid());
- 
-         // test threadDump/time can be parsed
-         ZonedDateTime.parse(threadDump.time());
- 
-         // test threadDump/runtimeVersion
-         assertEquals(Runtime.version().toString(), threadDump.runtimeVersion());
- 
-         // test root container, has no parent and no owner
-         var rootContainer = threadDump.rootThreadContainer();
-         assertFalse(rootContainer.owner().isPresent());
-         assertFalse(rootContainer.parent().isPresent());
- 
-         // test that the container contains the given thread
-         ThreadDump.ThreadContainer container;
-         if (containerName == null) {
-             // root container, the thread should be found if trackAllThreads is true
-             container = rootContainer;
-         } else {
-             // find the container
-             container = threadDump.findThreadContainer(containerName).orElse(null);
-             assertNotNull(container, containerName + " not found");
-             assertFalse(container.owner().isPresent());
-             assertTrue(container.parent().get() == rootContainer);
  
          }
-         boolean found = container.findThread(thread.threadId()).isPresent();
-         assertEquals(expectInDump, found);
- 
-         // current thread should be in root container if platform thread or tracking all threads
-         Thread currentThread = Thread.currentThread();
-         boolean currentThreadExpected = trackAllThreads || !currentThread.isVirtual();
-         found = rootContainer.findThread(currentThread.threadId()).isPresent();
-         assertEquals(currentThreadExpected, found);
      }
  
      /**
       * Test that dumpThreads throws if the output file already exists.
       */
--- 127,252 ---
                  Executors.newVirtualThreadPerTaskExecutor()
          );
      }
  
      /**
!      * Test that a thread container for an executor service is in the JSON format thread dump.
       */
!     @ParameterizedTest
!     @MethodSource("executors")
!     void testThreadContainer(ExecutorService executor) throws Exception {
!         try (executor) {
!             testThreadContainer(executor, Objects.toIdentityString(executor));
          }
      }
  
      /**
!      * Test that a thread container for the common pool is in the JSON format thread dump.
       */
      @Test
!     void testCommonPool() throws Exception {
!         testThreadContainer(ForkJoinPool.commonPool(), "ForkJoinPool.commonPool");
      }
  
      /**
!      * Test that the JSON thread dump has a thread container for the given executor.
       */
!     void testThreadContainer(ExecutorService executor, String name) throws Exception {
!         var threadRef = new AtomicReference<Thread>();
! 
!         executor.submit(() -> {
!             threadRef.set(Thread.currentThread());
!             LockSupport.park();
!         });
! 
!         // capture Thread
!         Thread thread;
+         while ((thread = threadRef.get()) == null) {
+             Thread.sleep(20);
+         }
+ 
+         try {
+             // dump threads to file and parse as JSON object
+             ThreadDump threadDump = dumpThreadsToJson();
+ 
+             // find the thread container corresponding to the executor
+             var container = threadDump.findThreadContainer(name).orElse(null);
+             assertNotNull(container, name + " not found");
+             assertFalse(container.owner().isPresent());
+             var parent = container.parent().orElseThrow();
+             assertEquals(threadDump.rootThreadContainer(), parent);
+ 
+             // find the thread in the thread container
+             ThreadDump.ThreadInfo ti = container.findThread(thread.threadId()).orElse(null);
+             assertNotNull(ti, "thread not found");
+ 
+         } finally {
+             LockSupport.unpark(thread);
          }
      }
  
      /**
!      * Test thread dump with a thread blocked on monitor enter.
       */
!     @Test
!     void testBlockedThread() throws Exception {
!         assumeTrue(trackAllThreads, "This test requires all virtual threads to be tracked");
!         var lock = new Object();
!         var started = new CountDownLatch(1);
! 
!         Thread vthread = Thread.ofVirtual().unstarted(() -> {
!             started.countDown();
!             synchronized (lock) { }  // blocks
+         });
+ 
+         long tid = vthread.threadId();
+         String lockAsString = Objects.toIdentityString(lock);
+ 
+         try {
+             synchronized (lock) {
+                 // start thread and wait for it to block
+                 vthread.start();
+                 started.await();
+                 await(vthread, Thread.State.BLOCKED);
+ 
+                 // thread dump in plain text should include thread
+                 List<String> lines = dumpThreadsToPlainText();
+                 ThreadFields fields = findThread(tid, lines);
+                 assertNotNull(fields, "thread not found");
+                 assertEquals("BLOCKED", fields.state());
+                 assertTrue(contains(lines, "// blocked on " + lockAsString));
+ 
+                 // thread dump in JSON format should include thread in root container
+                 ThreadDump threadDump = dumpThreadsToJson();
+                 ThreadDump.ThreadInfo ti = threadDump.rootThreadContainer()
+                         .findThread(tid)
+                         .orElse(null);
+                 assertNotNull(ti, "thread not found");
+                 assertEquals("BLOCKED", ti.state());
+                 assertEquals(lockAsString, ti.blockedOn());
              }
+         } finally {
+             vthread.join();
          }
      }
  
      /**
!      * Test thread dump with a thread waiting in Object.wait.
       */
      @Test
!     void testWaitingThread() throws Exception {
!         assumeTrue(trackAllThreads, "This test requires all virtual threads to be tracked");
!         var lock = new Object();
+         var started = new CountDownLatch(1);
+ 
+         Thread vthread = Thread.ofVirtual().start(() -> {
+             synchronized (lock) {
+                 started.countDown();
+                 try {
+                     lock.wait();
+                 } catch (InterruptedException e) { }
+             }
+         });
+ 
+         long tid = vthread.threadId();
+         String lockAsString = Objects.toIdentityString(lock);
+ 
          try {
!             // wait for thread to be waiting in Object.wait
+             started.await();
+             await(vthread, Thread.State.WAITING);
+ 
+             // thread dump in plain text should include thread
+             List<String> lines = dumpThreadsToPlainText();
+             ThreadFields fields = findThread(tid, lines);
+             assertNotNull(fields, "thread not found");
+             assertEquals("WAITING", fields.state());
+             assertTrue(contains(lines, "// waiting on " + lockAsString));
+ 
+             // thread dump in JSON format should include thread in root container
+             ThreadDump threadDump = dumpThreadsToJson();
+             ThreadDump.ThreadInfo ti = threadDump.rootThreadContainer()
+                     .findThread(vthread.threadId())
+                     .orElse(null);
+             assertNotNull(ti, "thread not found");
+             assertEquals("WAITING", ti.state());
+             assertEquals(Objects.toIdentityString(lock), ti.waitingOn());
+ 
          } finally {
!             synchronized (lock) {
+                 lock.notifyAll();
+             }
+             vthread.join();
          }
      }
  
      /**
!      * Test thread dump with a thread parked on a j.u.c. lock.
       */
!     @Test
!     void testParkedThread() throws Exception {
!         assumeTrue(trackAllThreads, "This test requires all virtual threads to be tracked");
!         var lock = new ReentrantLock();
!         var started = new CountDownLatch(1);
! 
!         Thread vthread = Thread.ofVirtual().unstarted(() -> {
!             started.countDown();
!             lock.lock();
!             lock.unlock();
!         });
  
!         long tid = vthread.threadId();
  
!         lock.lock();
!         try {
!             // start thread and wait for it to park
!             vthread.start();
+             started.await();
+             await(vthread, Thread.State.WAITING);
+ 
+             // thread dump in plain text should include thread
+             List<String> lines = dumpThreadsToPlainText();
+             ThreadFields fields = findThread(tid, lines);
+             assertNotNull(fields, "thread not found");
+             assertEquals("WAITING", fields.state());
+             assertTrue(contains(lines, "// parked on java.util.concurrent.locks.ReentrantLock"));
+ 
+             // thread dump in JSON format should include thread in root container
+             ThreadDump threadDump = dumpThreadsToJson();
+             ThreadDump.ThreadInfo ti = threadDump.rootThreadContainer()
+                     .findThread(vthread.threadId())
+                     .orElse(null);
+             assertNotNull(ti, "thread not found");
+             assertEquals("WAITING", ti.state());
+             String parkBlocker = ti.parkBlocker();
+             assertNotNull(parkBlocker);
+             assertTrue(parkBlocker.contains("java.util.concurrent.locks.ReentrantLock"));
+         } finally {
+             lock.unlock();
+         }
      }
  
      /**
!      * Test thread dump wth a thread owning a monitor.
       */
!     @Test
!     void testThreadOwnsMonitor() throws Exception {
!         assumeTrue(trackAllThreads, "This test requires all virtual threads to be tracked");
!         var lock = new Object();
!         var started = new CountDownLatch(1);
! 
!         Thread vthread = Thread.ofVirtual().start(() -> {
+             synchronized (lock) {
+                 started.countDown();
+                 LockSupport.park();
+             }
+         });
  
!         long tid = vthread.threadId();
!         String lockAsString = Objects.toIdentityString(lock);
  
+         try {
+             // wait for thread to park
+             started.await();
+             await(vthread, Thread.State.WAITING);
+ 
+             // thread dump in plain text should include thread
+             List<String> lines = dumpThreadsToPlainText();
+             ThreadFields fields = findThread(tid, lines);
+             assertNotNull(fields, "thread not found");
+             assertEquals("WAITING", fields.state());
+             assertTrue(contains(lines, "// locked " + lockAsString));
+ 
+             // thread dump in JSON format should include thread in root container
+             ThreadDump threadDump = dumpThreadsToJson();
+             ThreadDump.ThreadInfo ti = threadDump.rootThreadContainer()
+                     .findThread(tid)
+                     .orElse(null);
+             assertNotNull(ti, "thread not found");
+             // the lock should be in the ownedMonitors array
+             Set<String> ownedMonitors = ti.ownedMonitors().values()
+                     .stream()
+                     .flatMap(List::stream)
+                     .collect(Collectors.toSet());
+             assertTrue(ownedMonitors.contains(lockAsString), lockAsString + " not found");
+         } finally {
+             LockSupport.unpark(vthread);
          }
      }
  
      /**
       * Test that dumpThreads throws if the output file already exists.
       */

*** 283,35 ***
          assertThrows(NullPointerException.class,
                  () -> mbean.dumpThreads(genOutputPath("txt").toString(), null));
      }
  
      /**
!      * Submits a parking task to the given executor, returns the Thread object of
-      * the parked thread.
       */
!     private static Thread forkParker(ExecutorService executor) {
!         class Box { static volatile Thread thread;}
!         var latch = new CountDownLatch(1);
!         executor.submit(() -> {
!             Box.thread = Thread.currentThread();
!             latch.countDown();
!             LockSupport.park();
!         });
!         try {
!             latch.await();
!         } catch (InterruptedException e) {
!             throw new RuntimeException(e);
          }
!         return Box.thread;
      }
  
      /**
!      * Returns true if a Thread is present in a plain text thread dump.
       */
!     private static boolean isPresent(Path file, Thread thread) throws Exception {
!         String expect = "#" + thread.threadId();
!         return count(file, expect) > 0;
      }
  
      /**
       * Generate a file path with the given suffix to use as an output file.
       */
--- 409,64 ---
          assertThrows(NullPointerException.class,
                  () -> mbean.dumpThreads(genOutputPath("txt").toString(), null));
      }
  
      /**
!      * Represents the data for a thread found in a plain text thread dump.
       */
!     private record ThreadFields(long tid, String name, String state) { }
! 
!     /**
!      * Find a thread in the lines of a plain text thread dump.
!      */
!     private ThreadFields findThread(long tid, List<String> lines) {
!         String line = lines.stream()
!                 .filter(l -> l.startsWith("#" + tid + " "))
!                 .findFirst()
!                 .orElse(null);
!         if (line == null) {
!             return null;
          }
! 
+         // #3 "main" RUNNABLE 2025-04-18T15:22:12.012450Z
+         String[] components = line.split("\\s+");  // assume no spaces in thread name
+         assertEquals(4, components.length);
+         String nameInQuotes = components[1];
+         String name = nameInQuotes.substring(1, nameInQuotes.length()-1);
+         String state = components[2];
+         return new ThreadFields(tid, name, state);
+     }
+ 
+     /**
+      * Returns true if lines of a plain text thread dump contain the given text.
+      */
+     private boolean contains(List<String> lines, String text) {
+         return lines.stream().map(String::trim)
+                 .anyMatch(l -> l.contains(text));
      }
  
      /**
!      * Dump threads to a file in plain text format, return the lines in the file.
       */
!     private List<String> dumpThreadsToPlainText() throws Exception {
!         Path file = genOutputPath(".txt");
!         var mbean = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class);
+         mbean.dumpThreads(file.toString(), HotSpotDiagnosticMXBean.ThreadDumpFormat.TEXT_PLAIN);
+         System.err.format("Dumped to %s%n", file);
+         return Files.readAllLines(file);
+     }
+ 
+     /**
+      * Dump threads to a file in JSON format, parse and return as JSON object.
+      */
+     private static ThreadDump dumpThreadsToJson() throws Exception {
+         Path file = genOutputPath(".json");
+         var mbean = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class);
+         mbean.dumpThreads(file.toString(), HotSpotDiagnosticMXBean.ThreadDumpFormat.JSON);
+         System.err.format("Dumped to %s%n", file);
+         String jsonText = Files.readString(file);
+         return ThreadDump.parse(jsonText);
      }
  
      /**
       * Generate a file path with the given suffix to use as an output file.
       */

*** 321,23 ***
          Files.delete(file);
          return file;
      }
  
      /**
!      * Return the count of the number of files in the given file that contain
-      * the given character sequence.
-      */
-     static long count(Path file, CharSequence cs) throws Exception {
-         try (Stream<String> stream = Files.lines(file)) {
-             return stream.filter(line -> line.contains(cs)).count();
-         }
-     }
- 
-     /**
-      * Return line $n of the given file.
       */
!     private String line(Path file, long n) throws Exception {
!         try (Stream<String> stream = Files.lines(file)) {
!             return stream.skip(n).findFirst().orElseThrow();
          }
      }
  }
--- 476,16 ---
          Files.delete(file);
          return file;
      }
  
      /**
!      * Waits for the given thread to get to a given state.
       */
!     private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
!         Thread.State state = thread.getState();
!         while (state != expectedState) {
+             assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
+             Thread.sleep(10);
+             state = thread.getState();
          }
      }
  }
< prev index next >