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