1 /*
   2  * Copyright (c) 2003, 2018, 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 package nsk.monitoring.stress.thread;
  25 
  26 import java.lang.management.*;
  27 import java.io.*;
  28 import nsk.share.*;
  29 import nsk.monitoring.share.*;
  30 
  31 public class strace001 {
  32     public final static String LIB_NAME = "StackTraceController";
  33     private final static String THREAD_NAME
  34         = "nsk.monitoring.stress.thread.RunningThread";
  35     private final static int ITERATIONS = 50;
  36 
  37     public static volatile boolean finish;
  38     public static volatile boolean testFailed = false;
  39     public static Object common = new Object();
  40     public static Integer activeThreads;
  41     private static Log log;
  42     private static int depth;
  43     private static int threadCount;
  44     private static String[] expectedTrace;
  45     private static ThreadMonitor monitor;
  46     private static ThreadController controller;
  47 
  48     public static void main(String[] argv) {
  49         System.exit(run(argv, System.out) + Consts.JCK_STATUS_BASE);
  50     }
  51 
  52     public static int run(String[] argv, PrintStream out) {
  53         ArgumentHandler argHandler = new ArgumentHandler(argv);
  54         log = new Log(out, argHandler);
  55         monitor = Monitor.getThreadMonitor(log, argHandler);
  56         threadCount = argHandler.getThreadCount();
  57         depth = argHandler.getThreadDepth();
  58         controller = new ThreadController(log, threadCount, depth,
  59                                           argHandler.getInvocationType());
  60         RunningThread threads[] = new RunningThread[threadCount];
  61 
  62         // Fill expectedTrace array according to invocation type that is set in
  63         // test options
  64         if ( !fillTrace() ) {
  65             log.complain("Unknown invocation type: "
  66                        + controller.getInvocationType());
  67             return Consts.TEST_FAILED;
  68         }
  69 
  70         for (int i = 0; i < ITERATIONS; i++) {
  71             log.display("\nIteration: " + i);
  72             activeThreads = new Integer(0);
  73             finish = false;
  74 
  75             // Start all threads. Half of them are user threads,
  76             // others - deamon
  77             for (int j = 0; j < threadCount; j++) {
  78                 threads[j] = new RunningThread(j, controller, log, depth);
  79                 threads[j].setDaemon(i % 2 == 0);
  80                 threads[j].start();
  81             }
  82 
  83             // Wait for all threads to start
  84             while (activeThreads.intValue() < threadCount)
  85                 Thread.currentThread().yield();
  86             log.display("All threads started: " + activeThreads);
  87 
  88             // Make a snapshot of stack trace for all threads and check it
  89             for (int j = 0; j < threadCount; j++) {
  90                 boolean isAlive = threads[j].isAlive();
  91                 ThreadInfo info = monitor.getThreadInfo(threads[j].getId(), Integer.MAX_VALUE);
  92 
  93                 // A thread may be dead because of OutOfMemoryError or
  94                 // StackOverflowError
  95                 if (isAlive) {
  96                     if (info == null) {
  97                         log.complain("ThreadInfo for thread " + j + " is null, "
  98                                    + "but Thread.isAlive() returned true.");
  99                         testFailed = true;
 100                         continue;
 101                     }
 102 
 103                     StackTraceElement[] snapshot = info.getStackTrace();
 104                     if ( !checkTrace(snapshot) ) {
 105                         log.display("\nSnapshot of thread: " + j);
 106                         printStackTrace(snapshot);
 107                         testFailed = true;
 108                     }
 109                 } else {
 110                     log.display("Thread " + j + " is dead, skipping it.");
 111                 }
 112             }
 113 
 114             // Let all threads to complete their job
 115             finish = true;
 116 
 117             // Wait for all threads to be dead
 118             for (int j = 0; j < threadCount; j++)
 119                 try {
 120                     threads[j].join();
 121                 } catch (InterruptedException e) {
 122                     log.complain("Unexpected exception while joining thread "
 123                                + j);
 124                     e.printStackTrace(log.getOutStream());
 125                     testFailed = true;
 126                 }
 127             log.display("All threads have died.");
 128         } // for i
 129 
 130         if (testFailed)
 131             log.complain("TEST FAILED.");
 132 
 133         return (testFailed) ? Consts.TEST_FAILED : Consts.TEST_PASSED;
 134     }
 135 
 136     // Fill expectedTrace array according to the invocation type that is set in
 137     // test options
 138     private static boolean fillTrace() {
 139         switch (controller.getInvocationType()) {
 140             case ThreadController.JAVA_TYPE:
 141                 expectedTrace = new String[] {
 142                     "java.lang.Thread.sleep"
 143                     , "java.lang.Thread.yield"
 144                     , THREAD_NAME + ".waitForSign"
 145                     , THREAD_NAME + ".recursionJava"
 146                     , THREAD_NAME + ".run"
 147                 };
 148                 break;
 149 
 150             case ThreadController.NATIVE_TYPE:
 151                 expectedTrace = new String[] {
 152                     "java.lang.Thread.sleep"
 153                     , "java.lang.Thread.yield"
 154                     , THREAD_NAME + ".waitForSign"
 155                     , THREAD_NAME + ".recursionNative"
 156                     , THREAD_NAME + ".run"
 157                 };
 158                 break;
 159 
 160             case ThreadController.MIXED_TYPE:
 161                 expectedTrace = new String[] {
 162                     "java.lang.Thread.sleep"
 163                     , "java.lang.Thread.yield"
 164                     , THREAD_NAME + ".waitForSign"
 165                     , THREAD_NAME + ".recursionNative"
 166                     , THREAD_NAME + ".recursionJava"
 167                     , THREAD_NAME + ".run"
 168                 };
 169                 break;
 170 
 171             default:
 172                 return false;
 173         }
 174 
 175         return true;
 176     }
 177 
 178     // The method prints stack trace in style JVM does
 179     private static void printStackTrace(StackTraceElement[] elements) {
 180         for (int i = 0; i < elements.length; i++) {
 181             String s = "\t " + i + ": " + elements[i].getClassName() + "."
 182                      + elements[i].getMethodName();
 183 
 184             if (elements[i].isNativeMethod())
 185                 s = s + "(Native Method)";
 186             else
 187                 s = s + "(" + elements[i].getFileName() + ":"
 188                   + elements[i].getLineNumber() + ")";
 189             log.display(s);
 190         }
 191     }
 192 
 193     // The method performs checks of the stack trace
 194     private static boolean checkTrace(StackTraceElement[] elements) {
 195         int length = elements.length;
 196         int expectedLength = depth +3;
 197         boolean result = true;
 198 
 199         // Check the length of the trace. It must not be greater than
 200         // expectedLength. Number of recursionJava() or recursionNative()
 201         // methods must not ne greater than depth, also one Object.wait() or
 202         // Thread.yield() method, one run( ) and one waitForSign().
 203         if (length > expectedLength) {
 204             log.complain("Length of the stack trace is " + length + ", but "
 205                        + "expected to be not greater than " + expectedLength);
 206             result = false;
 207         }
 208 
 209         // Check each element of the snapshot
 210         for (int i = 0; i < elements.length; i++) {
 211             if (i == elements.length - 1) {
 212 
 213                 // The latest method of the snapshot must be RunningThread.run()
 214                 if ( !checkLastElement(elements[i]) )
 215                     result = false;
 216             } else {
 217 
 218                 // getClassName() and getMethodName() must return correct values
 219                 // for each element
 220                 if ( !checkElement(i, elements[i]) )
 221                     result = false;
 222             }
 223         }
 224         return result;
 225     }
 226 
 227     // The method checks that StackTraceElement.getClassName() and
 228     // StackTraceElement.getMethodName() return expected values
 229     private static boolean checkElement(int n, StackTraceElement element) {
 230         String name = element.getClassName() + "." + element.getMethodName();
 231 
 232         // The latest element is not checked, since it must be "run()"
 233         for (int i = 0; i < expectedTrace.length - 1; i++) {
 234             if (expectedTrace[i].equals(name))
 235                 return true;
 236         }
 237 
 238         log.complain("Unexpected " + n + " element of the stack trace:\n\t"
 239                    + name);
 240         return false;
 241     }
 242 
 243     // The method checks that StackTraceElement.getClassName() returns
 244     // "RunningThread" and StackTraceElement.getMethodName() returns "run"
 245     // for the latest element of the snapshot
 246     private static boolean checkLastElement(StackTraceElement element) {
 247         String name = element.getClassName() + "." + element.getMethodName();
 248         String last = expectedTrace[expectedTrace.length - 1];
 249 
 250         if (!last.equals(name)) {
 251             log.complain("Unexpected last element of the stack trace:\n\t"
 252                    + name + "\nexpected:\n\t" + last);
 253             return false;
 254         }
 255         return true;
 256     }
 257 }
 258 
 259 // This thread starts a recursion until it reaches specified depth. Then the
 260 // thread waits until it gets a notification from main thread. Pure java
 261 // and native methods are used in the thread. So, the thread is definitly in
 262 // "running" state when main thread performs its checks.
 263 class RunningThread extends Thread {
 264     private int num;
 265     private static ThreadController controller;
 266     private Log log;
 267     private int depth;
 268     private boolean mixed = false;
 269     native int recursionNative(int maxDepth, int currentDepth, boolean returnToJava);
 270 
 271     static {
 272         try {
 273             System.loadLibrary(strace001.LIB_NAME);
 274         } catch (UnsatisfiedLinkError e) {
 275             System.err.println("Cannot load library " + strace001.LIB_NAME);
 276             System.err.println("java.library.path: "
 277                              + System.getProperty("java.library.path"));
 278             throw e;
 279         }
 280     }
 281 
 282     RunningThread(int num, ThreadController controller, Log log, int depth) {
 283         this.num = num;
 284         this.controller = controller;
 285         this.log = log;
 286         this.depth = depth;
 287     }
 288 
 289     public void run() {
 290         int result = 0;
 291         int invocationType = controller.getInvocationType();
 292 
 293         // This instance of the thread is alive
 294         synchronized (strace001.common) {
 295             synchronized (strace001.activeThreads) {
 296                 strace001.activeThreads
 297                     = new Integer(strace001.activeThreads.intValue() + 1);
 298             }
 299         }
 300 
 301         // Choose a method (native or java) to continue recursion
 302         try {
 303             switch (invocationType) {
 304                 case ThreadController.JAVA_TYPE:
 305                     recursionJava(depth, 0);
 306                     break;
 307                 case ThreadController.NATIVE_TYPE:
 308                     result = recursionNative(depth, 0, false);
 309 
 310                     if (result == 1) {
 311                         log.display("Fatal error (OutOfMemoryError or "
 312                                 + "StackOverflow) is thrown in native method of "
 313                                 + " thread " + num);
 314                         return;
 315                     } else if (result == 2) {
 316                         log.complain("Unexpected exception is thrown in native "
 317                                 + "method of thread " + num);
 318                         strace001.testFailed = true;
 319                         return;
 320                     }
 321                     break;
 322                 case ThreadController.MIXED_TYPE:
 323                     mixed = true;
 324                     result = recursionNative(depth, 0, true);
 325 
 326                     if (result == 1) {
 327                         log.display("Fatal error (OutOfMemoryError or "
 328                                 + "StackOverflow) is thrown in native method of "
 329                                 + " thread " + num);
 330                         return;
 331                     } else if (result == 2) {
 332                         log.complain("Unexpected exception is thrown in native "
 333                                 + "method of thread " + num);
 334                         strace001.testFailed = true;
 335                         return;
 336                     }
 337                     break;
 338                 default:
 339                     log.complain("Unknown invocation type: "
 340                             + controller.getInvocationType());
 341                     strace001.testFailed = true;
 342             }
 343         } catch (OutOfMemoryError e) {
 344             // Recursion is too deep, so exit peacefully
 345             log.display("OutOfMemoryError is thrown in thread " + num);
 346         } catch (StackOverflowError e) {
 347             // Recursion is too deep, so exit peacefully
 348             log.display("StackOverflowError is thrown in thread " + num);
 349         }
 350     } // run()
 351 
 352     private void recursionJava(int maxDepth, int currentDepth) {
 353         // A short delay. Otherwise the method will reach the specified depth
 354         // almost instantly
 355         try {
 356             sleep(1);
 357         } catch (InterruptedException e) {
 358             log.complain("Unexpected exception");
 359             e.printStackTrace(log.getOutStream());
 360             strace001.testFailed = true;
 361         }
 362 
 363         currentDepth++;
 364         if (maxDepth > currentDepth) {
 365             yield();
 366             if (mixed) {
 367                 int result = recursionNative(maxDepth, currentDepth, true);
 368 
 369                  if (result == 1) {
 370                      log.display("Fatal error (OutOfMemoryError or "
 371                                + "StackOverflow) is thrown in native method of "
 372                                + " thread " + num);
 373                      return;
 374                  } else if (result == 2) {
 375                      log.complain("Unexpected exception is thrown in native "
 376                                 + "method of thread " + num);
 377                      strace001.testFailed = true;
 378                      return;
 379                  }
 380             } else
 381                 recursionJava(maxDepth, currentDepth);
 382         }
 383 
 384         waitForSign();
 385     } // recursionJava()
 386 
 387     private void waitForSign() {
 388         // When the depth is reached, wait for a notification from main thread
 389         while (!strace001.finish) {
 390             try {
 391                 sleep(1);
 392             } catch (InterruptedException e) {
 393                 log.complain("Unexpected exception");
 394                 e.printStackTrace(log.getOutStream());
 395                 strace001.testFailed = true;
 396                 break;
 397             }
 398         }
 399     } // waitForSign()
 400 } // class RunningThread