blob: 18ec9454ee01ebbe7b6f201a8ffda13d6abae42a [file] [log] [blame]
Mandy Chungeb2c6c52015-11-24 15:05:58 -08001/*
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
24import java.util.Arrays;
25import java.util.Collections;
26import java.util.List;
27import java.util.Set;
28import java.util.TreeSet;
29import java.util.concurrent.atomic.AtomicBoolean;
30import java.util.concurrent.atomic.AtomicLong;
31import java.lang.StackWalker.StackFrame;
32import 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 */
46public 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 {
Mandy Chungd80160d2015-12-17 09:39:21 -0800331 Env env = runTest(test, 1000, 10);
Mandy Chungeb2c6c52015-11-24 15:05:58 -0800332 //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}