1 /*
   2  * Copyright (c) 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 import java.util.Arrays;
  25 import java.util.Collections;
  26 import java.util.List;
  27 import java.util.Set;
  28 import java.util.TreeSet;
  29 import java.util.concurrent.atomic.AtomicBoolean;
  30 import java.util.concurrent.atomic.AtomicLong;
  31 import java.lang.StackWalker.StackFrame;
  32 import static java.lang.StackWalker.Option.*;
  33 
  34 
  35 /**
  36  * @test
  37  * @bug 8140450
  38  * @summary This test will walk the stack using different methods, called
  39  *          from several threads running concurrently.
  40  *          Except in the case of MTSTACKSTREAM - which takes a snapshot
  41  *          of the stack before walking, all the methods only allow to
  42  *          walk the current thread stack.
  43  * @run main/othervm MultiThreadStackWalk
  44  * @author danielfuchs
  45  */
  46 public class MultiThreadStackWalk {
  47 
  48     static Set<String> infrastructureClasses = new TreeSet<>(Arrays.asList(
  49             "sun.reflect.NativeMethodAccessorImpl",
  50             "sun.reflect.DelegatingMethodAccessorImpl",
  51             "java.lang.reflect.Method",
  52             "com.sun.javatest.regtest.MainWrapper$MainThread",
  53             "java.lang.Thread"
  54     ));
  55 
  56 
  57     static final List<Class<?>> streamPipelines = Arrays.asList(
  58             classForName("java.util.stream.AbstractPipeline"),
  59             classForName("java.util.stream.TerminalOp")
  60     );
  61 
  62     static Class<?> classForName(String name) {
  63         try {
  64             return Class.forName(name);
  65         } catch (ClassNotFoundException e){
  66             throw new RuntimeException(e);
  67         }
  68     }
  69 
  70     private static boolean isStreamPipeline(Class<?> clazz) {
  71         for (Class<?> c : streamPipelines) {
  72             if (c.isAssignableFrom(clazz)) {
  73                 return true;
  74             }
  75         }
  76         return false;
  77     }
  78 
  79     /**
  80      * An object that contains variables pertaining to the execution
  81      * of the test within one thread.
  82      * A small amount of those variable are shared with sub threads when
  83      * the stack walk is executed in parallel - that is when spliterators
  84      * obtained from trySplit are handed over to an instance of SplitThread
  85      * in order to parallelize thread walking.
  86      * @see WalkThread#handOff(MultiThreadStackWalk.Env, java.util.Spliterator, boolean, boolean)
  87      * @see Env#split(MultiThreadStackWalk.Env)
  88      */
  89     public static class Env {
  90         final AtomicLong frameCounter;  // private: the counter for the current thread.
  91         final long checkMarkAt;         // constant: the point at which we expect to
  92                                         // find the marker in consume()
  93         final long max;                 // constant: the maximum number of recursive
  94                                         // calls to Call.
  95         final AtomicBoolean debug ;     // shared: whether debug is active for the
  96                                         // instance of Test from which this instance
  97                                         // of Env was spawned
  98         final AtomicLong markerCalled;  // shared: whether the marker was reached
  99         final AtomicLong maxReached;    // shared: whether max was reached
 100         final Set<String> unexpected;   // shared: list of unexpected infrastructure
 101                                         // classes encountered after max is reached
 102 
 103         public Env(long total, long markAt, AtomicBoolean debug) {
 104             this.debug = debug;
 105             frameCounter = new AtomicLong();
 106             maxReached = new AtomicLong();
 107             unexpected = Collections.synchronizedSet(new TreeSet<>());
 108             this.max = total+2;
 109             this.checkMarkAt = total - markAt + 1;
 110             this.markerCalled = new AtomicLong();
 111         }
 112 
 113         // Used when delegating part of the stack walking to a sub thread
 114         // see WalkThread.handOff.
 115         private Env(Env orig, long start) {
 116             debug = orig.debug;
 117             frameCounter = new AtomicLong(start);
 118             maxReached = orig.maxReached;
 119             unexpected = orig.unexpected;
 120             max = orig.max;
 121             checkMarkAt = orig.checkMarkAt;
 122             markerCalled = orig.markerCalled;
 123         }
 124 
 125         // The stack walk consumer method, where all the checks are
 126         // performed.
 127         public void consume(StackFrame sfi) {
 128             if (frameCounter.get() == 0 && isStreamPipeline(sfi.getDeclaringClass())) {
 129                 return;
 130             }
 131 
 132             final long count = frameCounter.getAndIncrement();
 133             final StringBuilder builder = new StringBuilder();
 134             builder.append("Declaring class[")
 135                    .append(count)
 136                    .append("]: ")
 137                    .append(sfi.getDeclaringClass());
 138             builder.append('\n');
 139             builder.append("\t")
 140                    .append(sfi.getClassName())
 141                    .append(".")
 142                    .append(sfi.toStackTraceElement().getMethodName())
 143                    .append(sfi.toStackTraceElement().isNativeMethod()
 144                            ? "(native)"
 145                            : "(" + sfi.toStackTraceElement().getFileName()
 146                              +":"+sfi.toStackTraceElement().getLineNumber()+")");
 147             builder.append('\n');
 148             if (debug.get()) {
 149                 System.out.print("[debug] " + builder.toString());
 150                 builder.setLength(0);
 151             }
 152             if (count == max) {
 153                 maxReached.incrementAndGet();
 154             }
 155             if (count  == checkMarkAt) {
 156                 if (sfi.getDeclaringClass() != MultiThreadStackWalk.Marker.class) {
 157                     throw new RuntimeException("Expected Marker at " + count
 158                             + ", found " + sfi.getDeclaringClass());
 159                 }
 160             } else {
 161                 if (count <= 0 && sfi.getDeclaringClass() != MultiThreadStackWalk.Call.class) {
 162                     throw new RuntimeException("Expected Call at " + count
 163                             + ", found " + sfi.getDeclaringClass());
 164                 } else if (count > 0 && count < max && sfi.getDeclaringClass() != MultiThreadStackWalk.Test.class) {
 165                     throw new RuntimeException("Expected Test at " + count
 166                             + ", found " + sfi.getDeclaringClass());
 167                 } else if (count == max && sfi.getDeclaringClass() != MultiThreadStackWalk.class) {
 168                     throw new RuntimeException("Expected MultiThreadStackWalk at "
 169                             + count + ", found " + sfi.getDeclaringClass());
 170                 } else if (count == max &&  !sfi.toStackTraceElement().getMethodName().equals("runTest")) {
 171                     throw new RuntimeException("Expected runTest method at "
 172                             + count + ", found " + sfi.toStackTraceElement().getMethodName());
 173                 } else if (count == max+1) {
 174                     if (sfi.getDeclaringClass() != MultiThreadStackWalk.WalkThread.class) {
 175                         throw new RuntimeException("Expected MultiThreadStackWalk at "
 176                             + count + ", found " + sfi.getDeclaringClass());
 177                     }
 178                     if (count == max && !sfi.toStackTraceElement().getMethodName().equals("run")) {
 179                         throw new RuntimeException("Expected main method at "
 180                             + count + ", found " + sfi.toStackTraceElement().getMethodName());
 181                     }
 182                 } else if (count > max+1) {
 183                     // expect JTreg infrastructure...
 184                     if (!infrastructureClasses.contains(sfi.getDeclaringClass().getName())) {
 185                         System.err.println("**** WARNING: encountered unexpected infrastructure class at "
 186                                 + count +": " + sfi.getDeclaringClass().getName());
 187                         unexpected.add(sfi.getDeclaringClass().getName());
 188                     }
 189                 }
 190             }
 191             if (count == 100) {
 192                 // Maybe we should had some kind of checking inside that lambda
 193                 // too. For the moment we should be satisfied if it doesn't throw
 194                 // any exception and doesn't make the outer walk fail...
 195                 StackWalker.getInstance(RETAIN_CLASS_REFERENCE).forEach(x -> {
 196                     StackTraceElement st = x.toStackTraceElement();
 197                     StringBuilder b = new StringBuilder();
 198                     b.append("*** inner walk: ")
 199                             .append(x.getClassName())
 200                             .append(st == null ? "- no stack trace element -" :
 201                                     ("." + st.getMethodName()
 202                                             + (st.isNativeMethod() ? "(native)" :
 203                                             "(" + st.getFileName()
 204                                                     + ":" + st.getLineNumber() + ")")))
 205                             .append('\n');
 206                     if (debug.get()) {
 207                         System.out.print(b.toString());
 208                         b.setLength(0);
 209                     }
 210                 });
 211             }
 212         }
 213     }
 214 
 215     public interface Call {
 216         enum WalkType {
 217             WALKSTACK,         // use Thread.walkStack
 218         }
 219         default WalkType getWalkType() { return WalkType.WALKSTACK;}
 220         default void walk(Env env) {
 221             WalkType walktype = getWalkType();
 222             System.out.println("Thread "+ Thread.currentThread().getName()
 223                     +" starting walk with " + walktype);
 224             switch(walktype) {
 225                 case WALKSTACK:
 226                     StackWalker.getInstance(RETAIN_CLASS_REFERENCE)
 227                                .forEach(env::consume);
 228                     break;
 229                 default:
 230                     throw new InternalError("Unknown walk type: " + walktype);
 231             }
 232         }
 233         default void call(Env env, Call next, int total, int current, int markAt) {
 234             if (current < total) {
 235                 next.call(env, next, total, current+1, markAt);
 236             }
 237         }
 238     }
 239 
 240     public static class Marker implements Call {
 241         final WalkType walkType;
 242         Marker(WalkType walkType) {
 243             this.walkType = walkType;
 244         }
 245         @Override
 246         public WalkType getWalkType() {
 247             return walkType;
 248         }
 249 
 250         @Override
 251         public void call(Env env, Call next, int total, int current, int markAt) {
 252             env.markerCalled.incrementAndGet();
 253             if (current < total) {
 254                 next.call(env, next, total, current+1, markAt);
 255             } else {
 256                 next.walk(env);
 257             }
 258         }
 259     }
 260 
 261     public static class Test implements Call {
 262         final Marker marker;
 263         final WalkType walkType;
 264         final AtomicBoolean debug;
 265         Test(WalkType walkType) {
 266             this.walkType = walkType;
 267             this.marker = new Marker(walkType);
 268             this.debug = new AtomicBoolean();
 269         }
 270         @Override
 271         public WalkType getWalkType() {
 272             return walkType;
 273         }
 274         @Override
 275         public void call(Env env, Call next, int total, int current, int markAt) {
 276             if (current < total) {
 277                 int nexti = current + 1;
 278                 Call nextObj = nexti==markAt ? marker : next;
 279                 nextObj.call(env, next, total, nexti, markAt);
 280             } else {
 281                 walk(env);
 282             }
 283         }
 284     }
 285 
 286     public static Env runTest(Test test, int total, int markAt) {
 287         Env env = new Env(total, markAt, test.debug);
 288         test.call(env, test, total, 0, markAt);
 289         return env;
 290     }
 291 
 292     public static void checkTest(Env env, Test test) {
 293         String threadName = Thread.currentThread().getName();
 294         System.out.println(threadName + ": Marker called: " + env.markerCalled.get());
 295         System.out.println(threadName + ": Max reached: " + env.maxReached.get());
 296         System.out.println(threadName + ": Frames consumed: " + env.frameCounter.get());
 297         if (env.markerCalled.get() == 0) {
 298             throw new RuntimeException(Thread.currentThread().getName() + ": Marker was not called.");
 299         }
 300         if (env.markerCalled.get() > 1) {
 301             throw new RuntimeException(Thread.currentThread().getName()
 302                     + ": Marker was called more than once: " + env.maxReached.get());
 303         }
 304         if (!env.unexpected.isEmpty()) {
 305             System.out.flush();
 306             System.err.println("Encountered some unexpected infrastructure classes below 'main': "
 307                     + env.unexpected);
 308         }
 309         if (env.maxReached.get() == 0) {
 310             throw new RuntimeException(Thread.currentThread().getName()
 311                     + ": max not reached");
 312         }
 313         if (env.maxReached.get() > 1) {
 314             throw new RuntimeException(Thread.currentThread().getName()
 315                     + ": max was reached more than once: " + env.maxReached.get());
 316         }
 317     }
 318 
 319     static class WalkThread extends Thread {
 320         final static AtomicLong walkersCount = new AtomicLong();
 321         Throwable failed = null;
 322         final Test test;
 323         public WalkThread(Test test) {
 324             super("WalkThread[" + walkersCount.incrementAndGet() + ", type="
 325                     + test.getWalkType() + "]");
 326             this.test = test;
 327         }
 328 
 329         public void run() {
 330             try {
 331                 Env env = runTest(test, 2000, 10);
 332                 //waitWalkers(env);
 333                 checkTest(env, test);
 334             } catch(Throwable t) {
 335                 failed = t;
 336             }
 337         }
 338     }
 339 
 340     public static void main(String[] args) throws Throwable {
 341         WalkThread[] threads = new WalkThread[Call.WalkType.values().length*3];
 342         Throwable failed = null;
 343         for (int i=0; i<threads.length; i++) {
 344             Test test = new Test(Call.WalkType.values()[i%Call.WalkType.values().length]);
 345             threads[i] = new WalkThread(test);
 346         }
 347         for (int i=0; i<threads.length; i++) {
 348             threads[i].start();
 349         }
 350         for (int i=0; i<threads.length; i++) {
 351             threads[i].join();
 352             if (failed == null) failed = threads[i].failed;
 353             else if (threads[i].failed == null) {
 354                 failed.addSuppressed(threads[i].failed);
 355             }
 356         }
 357         if (failed != null) {
 358             throw failed;
 359         }
 360     }
 361 
 362 }