1 /*
   2  * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug     4530538
  27  * @summary Basic unit test of ThreadMXBean.getAllThreadIds()
  28  * @author  Alexei Guibadoulline and Mandy Chung
  29  *
  30  * @key intermittent
  31  * @modules java.management
  32  * @run main/othervm AllThreadIds
  33  */
  34 
  35 import java.lang.management.*;
  36 import java.time.Instant;
  37 import java.util.concurrent.Phaser;
  38 import java.util.function.Supplier;
  39 
  40 public class AllThreadIds {
  41     /**
  42      * A supplier wrapper for the delayed format printing.
  43      * The supplied value will have to be formatted as <em>$s</em>
  44      * @param <T> The wrapped type
  45      */
  46     private static final class ArgWrapper<T> {
  47         private final Supplier<T> val;
  48 
  49         public ArgWrapper(Supplier<T> val) {
  50             this.val = val;
  51         }
  52 
  53         @Override
  54         public String toString() {
  55             T resolved = val.get();
  56             return resolved != null ? resolved.toString() : null;
  57         }
  58     }
  59 
  60     static final int DAEMON_THREADS = 20;
  61     static final int USER_THREADS = 5;
  62     static final int ALL_THREADS = DAEMON_THREADS + USER_THREADS;
  63     private static final boolean live[] = new boolean[ALL_THREADS];
  64     private static final Thread allThreads[] = new Thread[ALL_THREADS];
  65     private static final ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
  66     private static boolean testFailed = false;
  67     private static boolean trace = false;
  68 
  69     private static long prevTotalThreadCount = 0;
  70     private static int prevLiveThreadCount = 0;
  71     private static int prevPeakThreadCount = 0;
  72 
  73     private static final Phaser startupCheck = new Phaser(ALL_THREADS + 1);
  74 
  75     private static void printThreadList() {
  76         long[] list = mbean.getAllThreadIds();
  77         for (int i = 1; i <= list.length; i++) {
  78             System.out.println(i + ": Thread id = " + list[i-1]);
  79         }
  80         for (int i = 0; i < ALL_THREADS; i++) {
  81             Thread t = allThreads[i];
  82             System.out.println(t.getName() + " Id = " + t.getId() +
  83                 " die = " + live[i] +
  84                 " alive = " + t.isAlive());
  85         }
  86     }
  87 
  88     private static void checkInitialState() throws Exception {
  89         updateCounters();
  90         checkThreadCount(0, 0);
  91     }
  92 
  93     private static void checkAllThreadsAlive() throws Exception {
  94         updateCounters();
  95 
  96         // Start all threads and wait to be sure they all are alive
  97         for (int i = 0; i < ALL_THREADS; i++) {
  98             setLive(i, true);
  99             allThreads[i] = new MyThread(i);
 100             allThreads[i].setDaemon(i < DAEMON_THREADS);
 101             allThreads[i].start();
 102         }
 103         // wait until all threads are started.
 104         startupCheck.arriveAndAwaitAdvance();
 105 
 106         checkThreadCount(ALL_THREADS, 0);
 107         if (trace) {
 108             printThreadList();
 109         }
 110         // Check mbean now. All threads must appear in getAllThreadIds() list
 111         long[] list = mbean.getAllThreadIds();
 112 
 113         for (int i = 0; i < ALL_THREADS; i++) {
 114             long expectedId = allThreads[i].getId();
 115             boolean found = false;
 116 
 117             if (trace) {
 118                 System.out.print("Looking for thread with id " + expectedId);
 119             }
 120             for (int j = 0; j < list.length; j++) {
 121                 if (expectedId == list[j]) {
 122                     found = true;
 123                     break;
 124                 }
 125             }
 126 
 127             if (!found) {
 128                 testFailed = true;
 129             }
 130             if (trace) {
 131                 if (!found) {
 132                     System.out.print(". TEST FAILED.");
 133                 }
 134                 System.out.println();
 135             }
 136         }
 137         if (trace) {
 138             System.out.println();
 139         }
 140     }
 141 
 142     private static void checkDaemonThreadsDead() throws Exception {
 143         updateCounters();
 144 
 145         // Stop daemon threads, wait to be sure they all are dead, and check
 146         // that they disappeared from getAllThreadIds() list
 147         for (int i = 0; i < DAEMON_THREADS; i++) {
 148             setLive(i, false);
 149         }
 150 
 151         // make sure the daemon threads are completely dead
 152         joinDaemonThreads();
 153 
 154         // and check the reported thread count
 155         checkThreadCount(0, DAEMON_THREADS);
 156 
 157         // Check mbean now
 158         long[] list = mbean.getAllThreadIds();
 159 
 160         for (int i = 0; i < ALL_THREADS; i++) {
 161             long expectedId = allThreads[i].getId();
 162             boolean found = false;
 163             boolean alive = (i >= DAEMON_THREADS);
 164 
 165             if (trace) {
 166                 System.out.print("Looking for thread with id " + expectedId +
 167                     (alive ? " expected alive." : " expected terminated."));
 168             }
 169             for (int j = 0; j < list.length; j++) {
 170                 if (expectedId == list[j]) {
 171                     found = true;
 172                     break;
 173                 }
 174             }
 175 
 176             if (alive != found) {
 177                 testFailed = true;
 178             }
 179             if (trace) {
 180                 if (alive != found) {
 181                     System.out.println(" TEST FAILED.");
 182                 } else {
 183                     System.out.println();
 184                 }
 185             }
 186         }
 187     }
 188 
 189     private static void checkAllThreadsDead() throws Exception {
 190         updateCounters();
 191 
 192         // Stop all threads and wait to be sure they all are dead
 193         for (int i = DAEMON_THREADS; i < ALL_THREADS; i++) {
 194             setLive(i, false);
 195         }
 196 
 197         // make sure the non-daemon threads are completely dead
 198         joinNonDaemonThreads();
 199 
 200         // and check the thread count
 201         checkThreadCount(0, ALL_THREADS - DAEMON_THREADS);
 202     }
 203 
 204     private static void checkThreadCount(int numNewThreads,
 205                                          int numTerminatedThreads)
 206         throws Exception {
 207 
 208         checkLiveThreads(numNewThreads, numTerminatedThreads);
 209         checkPeakThreads(numNewThreads);
 210         checkTotalThreads(numNewThreads);
 211         checkThreadIds();
 212     }
 213 
 214     private static void checkLiveThreads(int numNewThreads,
 215                                          int numTerminatedThreads)
 216         throws InterruptedException {
 217         int diff = numNewThreads - numTerminatedThreads;
 218 
 219         waitTillEquals(
 220             diff + prevLiveThreadCount,
 221             ()->(long)mbean.getThreadCount(),
 222             "Unexpected number of live threads: " +
 223                 " Prev live = %1$d Current live = ${provided} Threads added = %2$d" +
 224                 " Threads terminated = %3$d",
 225             ()->prevLiveThreadCount,
 226             ()->numNewThreads,
 227             ()->numTerminatedThreads
 228         );
 229     }
 230 
 231     private static void checkPeakThreads(int numNewThreads)
 232         throws InterruptedException {
 233 
 234         waitTillEquals(numNewThreads + prevPeakThreadCount,
 235             ()->(long)mbean.getPeakThreadCount(),
 236             "Unexpected number of peak threads: " +
 237                 " Prev peak = %1$d Current peak = ${provided} Threads added = %2$d",
 238             ()->prevPeakThreadCount,
 239             ()->numNewThreads
 240         );
 241     }
 242 
 243     private static void checkTotalThreads(int numNewThreads)
 244         throws InterruptedException {
 245 
 246         waitTillEquals(numNewThreads + prevTotalThreadCount,
 247             ()->mbean.getTotalStartedThreadCount(),
 248             "Unexpected number of total threads: " +
 249                 " Prev Total = %1$d Current Total = ${provided} Threads added = %2$d",
 250             ()->prevTotalThreadCount,
 251             ()->numNewThreads
 252         );
 253     }
 254 
 255     private static void checkThreadIds() throws InterruptedException {
 256         long[] list = mbean.getAllThreadIds();
 257 
 258         waitTillEquals(
 259             list.length,
 260             ()->(long)mbean.getThreadCount(),
 261             "Array length returned by " +
 262                 "getAllThreadIds() = %1$d not matched count = ${provided}",
 263             ()->list.length
 264         );
 265     }
 266 
 267     /**
 268      * Waits till the <em>expectedVal</em> equals to the <em>retrievedVal</em>.
 269      * It will report a status message on the first occasion of the value mismatch
 270      * and then, subsequently, when the <em>retrievedVal</em> value changes.
 271      * @param expectedVal The value to wait for
 272      * @param retrievedVal The supplier of the value to check against the <em>expectedVal</em>
 273      * @param msgFormat The formatted message to be printed in case of mismatch
 274      * @param msgArgs The parameters to the formatted message
 275      * @throws InterruptedException
 276      */
 277     private static void waitTillEquals(long expectedVal, Supplier<Long> retrievedVal,
 278                                         String msgFormat, Supplier<Object> ... msgArgs)
 279         throws InterruptedException {
 280         Object[] args = null;
 281 
 282         long countPrev = -1;
 283         while (true) {
 284             Long count = retrievedVal.get();
 285             if (count == expectedVal) break;
 286             if (countPrev == -1 || countPrev != count) {
 287                 if (args == null) {
 288                     args = new Object[msgArgs.length];
 289                     for(int i=0; i < msgArgs.length; i++) {
 290                         args[i] = new ArgWrapper<>((Supplier<Object>)msgArgs[i]);
 291                     }
 292                 }
 293                 System.err.format("TS: %s\n", Instant.now());
 294                 System.err.format(
 295                     msgFormat
 296                         .replace("${provided}", String.valueOf(count))
 297                         .replace("$d", "$s"),
 298                     args
 299                 ).flush();
 300                 printThreadList();
 301                 System.err.println("\nRetrying ...\n");
 302             }
 303             countPrev = count;
 304             Thread.sleep(1);
 305         }
 306     }
 307 
 308     private static void updateCounters() {
 309         prevTotalThreadCount = mbean.getTotalStartedThreadCount();
 310         prevLiveThreadCount = mbean.getThreadCount();
 311         prevPeakThreadCount = mbean.getPeakThreadCount();
 312     }
 313 
 314     public static void main(String args[]) throws Exception {
 315         if (args.length > 0 && args[0].equals("trace")) {
 316             trace = true;
 317         }
 318 
 319         checkInitialState();
 320         checkAllThreadsAlive();
 321         checkDaemonThreadsDead();
 322         checkAllThreadsDead();
 323 
 324         if (testFailed)
 325             throw new RuntimeException("TEST FAILED.");
 326 
 327         System.out.println("Test passed.");
 328     }
 329 
 330     private static void joinDaemonThreads() throws InterruptedException {
 331         for (int i = 0; i < DAEMON_THREADS; i++) {
 332             allThreads[i].join();
 333         }
 334     }
 335 
 336     private static void joinNonDaemonThreads() throws InterruptedException {
 337         for (int i = DAEMON_THREADS; i < ALL_THREADS; i++) {
 338             allThreads[i].join();
 339         }
 340     }
 341 
 342     private static void setLive(int i, boolean val) {
 343         synchronized(live) {
 344             live[i] = val;
 345         }
 346     }
 347 
 348     private static boolean isLive(int i) {
 349         synchronized(live) {
 350             return live[i];
 351         }
 352     }
 353 
 354     // The MyThread thread lives as long as correspondent live[i] value is true
 355     private static class MyThread extends Thread {
 356         int id;
 357 
 358         MyThread(int id) {
 359             this.id = id;
 360         }
 361 
 362         public void run() {
 363             // signal started
 364             startupCheck.arrive();
 365             while (isLive(id)) {
 366                 try {
 367                     sleep(100);
 368                 } catch (InterruptedException e) {
 369                     System.out.println("Unexpected exception is thrown.");
 370                     e.printStackTrace(System.out);
 371                     testFailed = true;
 372                 }
 373             }
 374         }
 375     }
 376 }