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             "jdk.internal.reflect.NativeMethodAccessorImpl",
50             "jdk.internal.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 
classForName(String name)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 
isStreamPipeline(Class<?> clazz)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 
Env(long total, long markAt, AtomicBoolean debug)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.
Env(Env orig, long start)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.
consume(StackFrame sfi)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         }
getWalkType()219         default WalkType getWalkType() { return WalkType.WALKSTACK;}
walk(Env env)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         }
call(Env env, Call next, int total, int current, int markAt)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;
Marker(WalkType walkType)242         Marker(WalkType walkType) {
243             this.walkType = walkType;
244         }
245         @Override
getWalkType()246         public WalkType getWalkType() {
247             return walkType;
248         }
249 
250         @Override
call(Env env, Call next, int total, int current, int markAt)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;
Test(WalkType walkType)265         Test(WalkType walkType) {
266             this.walkType = walkType;
267             this.marker = new Marker(walkType);
268             this.debug = new AtomicBoolean();
269         }
270         @Override
getWalkType()271         public WalkType getWalkType() {
272             return walkType;
273         }
274         @Override
call(Env env, Call next, int total, int current, int markAt)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 
runTest(Test test, int total, int markAt)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 
checkTest(Env env, Test test)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;
WalkThread(Test test)323         public WalkThread(Test test) {
324             super("WalkThread[" + walkersCount.incrementAndGet() + ", type="
325                     + test.getWalkType() + "]");
326             this.test = test;
327         }
328 
run()329         public void run() {
330             try {
331                 Env env = runTest(test, 1000, 10);
332                 //waitWalkers(env);
333                 checkTest(env, test);
334             } catch(Throwable t) {
335                 failed = t;
336             }
337         }
338     }
339 
main(String[] args)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 }
363