--- /dev/null 2015-11-13 07:09:43.000000000 -0800 +++ new/jdk/test/java/lang/StackWalker/MultiThreadStackWalk.java 2015-11-13 07:09:43.000000000 -0800 @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.lang.StackWalker.StackFrame; +import static java.lang.StackWalker.Option.*; + + +/** + * @test + * @bug 8140450 + * @summary This test will walk the stack using different methods, called + * from several threads running concurrently. + * Except in the case of MTSTACKSTREAM - which takes a snapshot + * of the stack before walking, all the methods only allow to + * walk the current thread stack. + * @run main/othervm MultiThreadStackWalk + * @author danielfuchs + */ +public class MultiThreadStackWalk { + + static Set infrastructureClasses = new TreeSet<>(Arrays.asList( + "sun.reflect.NativeMethodAccessorImpl", + "sun.reflect.DelegatingMethodAccessorImpl", + "java.lang.reflect.Method", + "com.sun.javatest.regtest.MainWrapper$MainThread", + "java.lang.Thread" + )); + + + static final List> streamPipelines = Arrays.asList( + classForName("java.util.stream.AbstractPipeline"), + classForName("java.util.stream.TerminalOp") + ); + + static Class classForName(String name) { + try { + return Class.forName(name); + } catch (ClassNotFoundException e){ + throw new RuntimeException(e); + } + } + + private static boolean isStreamPipeline(Class clazz) { + for (Class c : streamPipelines) { + if (c.isAssignableFrom(clazz)) { + return true; + } + } + return false; + } + + /** + * An object that contains variables pertaining to the execution + * of the test within one thread. + * A small amount of those variable are shared with sub threads when + * the stack walk is executed in parallel - that is when spliterators + * obtained from trySplit are handed over to an instance of SplitThread + * in order to parallelize thread walking. + * @see WalkThread#handOff(MultiThreadStackWalk.Env, java.util.Spliterator, boolean, boolean) + * @see Env#split(MultiThreadStackWalk.Env) + */ + public static class Env { + final AtomicLong frameCounter; // private: the counter for the current thread. + final long checkMarkAt; // constant: the point at which we expect to + // find the marker in consume() + final long max; // constant: the maximum number of recursive + // calls to Call. + final AtomicBoolean debug ; // shared: whether debug is active for the + // instance of Test from which this instance + // of Env was spawned + final AtomicLong markerCalled; // shared: whether the marker was reached + final AtomicLong maxReached; // shared: whether max was reached + final Set unexpected; // shared: list of unexpected infrastructure + // classes encountered after max is reached + + public Env(long total, long markAt, AtomicBoolean debug) { + this.debug = debug; + frameCounter = new AtomicLong(); + maxReached = new AtomicLong(); + unexpected = Collections.synchronizedSet(new TreeSet<>()); + this.max = total+2; + this.checkMarkAt = total - markAt + 1; + this.markerCalled = new AtomicLong(); + } + + // Used when delegating part of the stack walking to a sub thread + // see WalkThread.handOff. + private Env(Env orig, long start) { + debug = orig.debug; + frameCounter = new AtomicLong(start); + maxReached = orig.maxReached; + unexpected = orig.unexpected; + max = orig.max; + checkMarkAt = orig.checkMarkAt; + markerCalled = orig.markerCalled; + } + + // The stack walk consumer method, where all the checks are + // performed. + public void consume(StackFrame sfi) { + if (frameCounter.get() == 0 && isStreamPipeline(sfi.getDeclaringClass())) { + return; + } + + final long count = frameCounter.getAndIncrement(); + final StringBuilder builder = new StringBuilder(); + builder.append("Declaring class[") + .append(count) + .append("]: ") + .append(sfi.getDeclaringClass()); + builder.append('\n'); + builder.append("\t") + .append(sfi.getClassName()) + .append(".") + .append(sfi.toStackTraceElement().getMethodName()) + .append(sfi.toStackTraceElement().isNativeMethod() + ? "(native)" + : "(" + sfi.toStackTraceElement().getFileName() + +":"+sfi.toStackTraceElement().getLineNumber()+")"); + builder.append('\n'); + if (debug.get()) { + System.out.print("[debug] " + builder.toString()); + builder.setLength(0); + } + if (count == max) { + maxReached.incrementAndGet(); + } + if (count == checkMarkAt) { + if (sfi.getDeclaringClass() != MultiThreadStackWalk.Marker.class) { + throw new RuntimeException("Expected Marker at " + count + + ", found " + sfi.getDeclaringClass()); + } + } else { + if (count <= 0 && sfi.getDeclaringClass() != MultiThreadStackWalk.Call.class) { + throw new RuntimeException("Expected Call at " + count + + ", found " + sfi.getDeclaringClass()); + } else if (count > 0 && count < max && sfi.getDeclaringClass() != MultiThreadStackWalk.Test.class) { + throw new RuntimeException("Expected Test at " + count + + ", found " + sfi.getDeclaringClass()); + } else if (count == max && sfi.getDeclaringClass() != MultiThreadStackWalk.class) { + throw new RuntimeException("Expected MultiThreadStackWalk at " + + count + ", found " + sfi.getDeclaringClass()); + } else if (count == max && !sfi.toStackTraceElement().getMethodName().equals("runTest")) { + throw new RuntimeException("Expected runTest method at " + + count + ", found " + sfi.toStackTraceElement().getMethodName()); + } else if (count == max+1) { + if (sfi.getDeclaringClass() != MultiThreadStackWalk.WalkThread.class) { + throw new RuntimeException("Expected MultiThreadStackWalk at " + + count + ", found " + sfi.getDeclaringClass()); + } + if (count == max && !sfi.toStackTraceElement().getMethodName().equals("run")) { + throw new RuntimeException("Expected main method at " + + count + ", found " + sfi.toStackTraceElement().getMethodName()); + } + } else if (count > max+1) { + // expect JTreg infrastructure... + if (!infrastructureClasses.contains(sfi.getDeclaringClass().getName())) { + System.err.println("**** WARNING: encountered unexpected infrastructure class at " + + count +": " + sfi.getDeclaringClass().getName()); + unexpected.add(sfi.getDeclaringClass().getName()); + } + } + } + if (count == 100) { + // Maybe we should had some kind of checking inside that lambda + // too. For the moment we should be satisfied if it doesn't throw + // any exception and doesn't make the outer walk fail... + StackWalker.create(RETAIN_CLASS_REFERENCE).forEach(x -> { + StackTraceElement st = x.toStackTraceElement(); + StringBuilder b = new StringBuilder(); + b.append("*** inner walk: ") + .append(x.getClassName()) + .append(st == null ? "- no stack trace element -" : + ("." + st.getMethodName() + + (st.isNativeMethod() ? "(native)" : + "(" + st.getFileName() + + ":" + st.getLineNumber() + ")"))) + .append('\n'); + if (debug.get()) { + System.out.print(b.toString()); + b.setLength(0); + } + }); + } + } + } + + public interface Call { + enum WalkType { + WALKSTACK, // use Thread.walkStack + } + default WalkType getWalkType() { return WalkType.WALKSTACK;} + default void walk(Env env) { + WalkType walktype = getWalkType(); + System.out.println("Thread "+ Thread.currentThread().getName() + +" starting walk with " + walktype); + switch(walktype) { + case WALKSTACK: + StackWalker.create(RETAIN_CLASS_REFERENCE) + .forEach(env::consume); + break; + default: + throw new InternalError("Unknown walk type: " + walktype); + } + } + default void call(Env env, Call next, int total, int current, int markAt) { + if (current < total) { + next.call(env, next, total, current+1, markAt); + } + } + } + + public static class Marker implements Call { + final WalkType walkType; + Marker(WalkType walkType) { + this.walkType = walkType; + } + @Override + public WalkType getWalkType() { + return walkType; + } + + @Override + public void call(Env env, Call next, int total, int current, int markAt) { + env.markerCalled.incrementAndGet(); + if (current < total) { + next.call(env, next, total, current+1, markAt); + } else { + next.walk(env); + } + } + } + + public static class Test implements Call { + final Marker marker; + final WalkType walkType; + final AtomicBoolean debug; + Test(WalkType walkType) { + this.walkType = walkType; + this.marker = new Marker(walkType); + this.debug = new AtomicBoolean(); + } + @Override + public WalkType getWalkType() { + return walkType; + } + @Override + public void call(Env env, Call next, int total, int current, int markAt) { + if (current < total) { + int nexti = current + 1; + Call nextObj = nexti==markAt ? marker : next; + nextObj.call(env, next, total, nexti, markAt); + } else { + walk(env); + } + } + } + + public static Env runTest(Test test, int total, int markAt) { + Env env = new Env(total, markAt, test.debug); + test.call(env, test, total, 0, markAt); + return env; + } + + public static void checkTest(Env env, Test test) { + System.out.println(Thread.currentThread().getName() + ": Marker called: " + env.markerCalled.get()); + System.out.println(Thread.currentThread().getName() + ": Max reached: " + env.maxReached.get()); + System.out.println(Thread.currentThread().getName() + ": Frames consumed: " + env.frameCounter.get()); + if (env.markerCalled.get() == 0) { + throw new RuntimeException(Thread.currentThread().getName() + ": Marker was not called."); + } + if (env.markerCalled.get() > 1) { + throw new RuntimeException(Thread.currentThread().getName() + + ": Marker was called smore than once: " + env.maxReached.get()); + } + if (!env.unexpected.isEmpty()) { + System.out.flush(); + System.err.println("Encountered some unexpected infrastructure classes below 'main': " + + env.unexpected); + } + if (env.maxReached.get() == 0) { + throw new RuntimeException(Thread.currentThread().getName() + + ": max not reached"); + } + if (env.maxReached.get() > 1) { + throw new RuntimeException(Thread.currentThread().getName() + + ": max was reached more than once: " + env.maxReached.get()); + } + } + + static class WalkThread extends Thread { + final static AtomicLong walkersCount = new AtomicLong(); + Throwable failed = null; + final Test test; + public WalkThread(Test test) { + super("WalkThread[" + walkersCount.incrementAndGet() + ", type=" + + test.getWalkType() + "]"); + this.test = test; + } + + public void run() { + try { + Env env = runTest(test, 2000, 10); + //waitWalkers(env); + checkTest(env, test); + } catch(Throwable t) { + failed = t; + } + } + } + + public static void main(String[] args) throws Throwable { + WalkThread[] threads = new WalkThread[Call.WalkType.values().length*3]; + Throwable failed = null; + for (int i=0; i