Path: blob/master/test/jdk/java/lang/StackWalker/MultiThreadStackWalk.java
41149 views
/*1* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223import java.util.Arrays;24import java.util.Collections;25import java.util.List;26import java.util.Set;27import java.util.TreeSet;28import java.util.concurrent.atomic.AtomicBoolean;29import java.util.concurrent.atomic.AtomicLong;30import java.lang.StackWalker.StackFrame;31import static java.lang.StackWalker.Option.*;323334/**35* @test36* @bug 814045037* @summary This test will walk the stack using different methods, called38* from several threads running concurrently.39* Except in the case of MTSTACKSTREAM - which takes a snapshot40* of the stack before walking, all the methods only allow to41* walk the current thread stack.42* @run main/othervm MultiThreadStackWalk43* @author danielfuchs44*/45public class MultiThreadStackWalk {4647static Set<String> infrastructureClasses = new TreeSet<>(Arrays.asList(48"jdk.internal.reflect.NativeMethodAccessorImpl",49"jdk.internal.reflect.DelegatingMethodAccessorImpl",50"java.lang.reflect.Method",51"com.sun.javatest.regtest.MainWrapper$MainThread",52"java.lang.Thread"53));545556static final List<Class<?>> streamPipelines = Arrays.asList(57classForName("java.util.stream.AbstractPipeline"),58classForName("java.util.stream.TerminalOp")59);6061static Class<?> classForName(String name) {62try {63return Class.forName(name);64} catch (ClassNotFoundException e){65throw new RuntimeException(e);66}67}6869private static boolean isStreamPipeline(Class<?> clazz) {70for (Class<?> c : streamPipelines) {71if (c.isAssignableFrom(clazz)) {72return true;73}74}75return false;76}7778/**79* An object that contains variables pertaining to the execution80* of the test within one thread.81* A small amount of those variable are shared with sub threads when82* the stack walk is executed in parallel - that is when spliterators83* obtained from trySplit are handed over to an instance of SplitThread84* in order to parallelize thread walking.85* @see WalkThread#handOff(MultiThreadStackWalk.Env, java.util.Spliterator, boolean, boolean)86* @see Env#split(MultiThreadStackWalk.Env)87*/88public static class Env {89final AtomicLong frameCounter; // private: the counter for the current thread.90final long checkMarkAt; // constant: the point at which we expect to91// find the marker in consume()92final long max; // constant: the maximum number of recursive93// calls to Call.94final AtomicBoolean debug ; // shared: whether debug is active for the95// instance of Test from which this instance96// of Env was spawned97final AtomicLong markerCalled; // shared: whether the marker was reached98final AtomicLong maxReached; // shared: whether max was reached99final Set<String> unexpected; // shared: list of unexpected infrastructure100// classes encountered after max is reached101102public Env(long total, long markAt, AtomicBoolean debug) {103this.debug = debug;104frameCounter = new AtomicLong();105maxReached = new AtomicLong();106unexpected = Collections.synchronizedSet(new TreeSet<>());107this.max = total+2;108this.checkMarkAt = total - markAt + 1;109this.markerCalled = new AtomicLong();110}111112// Used when delegating part of the stack walking to a sub thread113// see WalkThread.handOff.114private Env(Env orig, long start) {115debug = orig.debug;116frameCounter = new AtomicLong(start);117maxReached = orig.maxReached;118unexpected = orig.unexpected;119max = orig.max;120checkMarkAt = orig.checkMarkAt;121markerCalled = orig.markerCalled;122}123124// The stack walk consumer method, where all the checks are125// performed.126public void consume(StackFrame sfi) {127if (frameCounter.get() == 0 && isStreamPipeline(sfi.getDeclaringClass())) {128return;129}130131final long count = frameCounter.getAndIncrement();132final StringBuilder builder = new StringBuilder();133builder.append("Declaring class[")134.append(count)135.append("]: ")136.append(sfi.getDeclaringClass());137builder.append('\n');138builder.append("\t")139.append(sfi.getClassName())140.append(".")141.append(sfi.toStackTraceElement().getMethodName())142.append(sfi.toStackTraceElement().isNativeMethod()143? "(native)"144: "(" + sfi.toStackTraceElement().getFileName()145+":"+sfi.toStackTraceElement().getLineNumber()+")");146builder.append('\n');147if (debug.get()) {148System.out.print("[debug] " + builder.toString());149builder.setLength(0);150}151if (count == max) {152maxReached.incrementAndGet();153}154if (count == checkMarkAt) {155if (sfi.getDeclaringClass() != MultiThreadStackWalk.Marker.class) {156throw new RuntimeException("Expected Marker at " + count157+ ", found " + sfi.getDeclaringClass());158}159} else {160if (count <= 0 && sfi.getDeclaringClass() != MultiThreadStackWalk.Call.class) {161throw new RuntimeException("Expected Call at " + count162+ ", found " + sfi.getDeclaringClass());163} else if (count > 0 && count < max && sfi.getDeclaringClass() != MultiThreadStackWalk.Test.class) {164throw new RuntimeException("Expected Test at " + count165+ ", found " + sfi.getDeclaringClass());166} else if (count == max && sfi.getDeclaringClass() != MultiThreadStackWalk.class) {167throw new RuntimeException("Expected MultiThreadStackWalk at "168+ count + ", found " + sfi.getDeclaringClass());169} else if (count == max && !sfi.toStackTraceElement().getMethodName().equals("runTest")) {170throw new RuntimeException("Expected runTest method at "171+ count + ", found " + sfi.toStackTraceElement().getMethodName());172} else if (count == max+1) {173if (sfi.getDeclaringClass() != MultiThreadStackWalk.WalkThread.class) {174throw new RuntimeException("Expected MultiThreadStackWalk at "175+ count + ", found " + sfi.getDeclaringClass());176}177if (count == max && !sfi.toStackTraceElement().getMethodName().equals("run")) {178throw new RuntimeException("Expected main method at "179+ count + ", found " + sfi.toStackTraceElement().getMethodName());180}181} else if (count > max+1) {182// expect JTreg infrastructure...183if (!infrastructureClasses.contains(sfi.getDeclaringClass().getName())) {184System.err.println("**** WARNING: encountered unexpected infrastructure class at "185+ count +": " + sfi.getDeclaringClass().getName());186unexpected.add(sfi.getDeclaringClass().getName());187}188}189}190if (count == 100) {191// Maybe we should had some kind of checking inside that lambda192// too. For the moment we should be satisfied if it doesn't throw193// any exception and doesn't make the outer walk fail...194StackWalker.getInstance(RETAIN_CLASS_REFERENCE).forEach(x -> {195StackTraceElement st = x.toStackTraceElement();196StringBuilder b = new StringBuilder();197b.append("*** inner walk: ")198.append(x.getClassName())199.append(st == null ? "- no stack trace element -" :200("." + st.getMethodName()201+ (st.isNativeMethod() ? "(native)" :202"(" + st.getFileName()203+ ":" + st.getLineNumber() + ")")))204.append('\n');205if (debug.get()) {206System.out.print(b.toString());207b.setLength(0);208}209});210}211}212}213214public interface Call {215enum WalkType {216WALKSTACK, // use Thread.walkStack217}218default WalkType getWalkType() { return WalkType.WALKSTACK;}219default void walk(Env env) {220WalkType walktype = getWalkType();221System.out.println("Thread "+ Thread.currentThread().getName()222+" starting walk with " + walktype);223switch(walktype) {224case WALKSTACK:225StackWalker.getInstance(RETAIN_CLASS_REFERENCE)226.forEach(env::consume);227break;228default:229throw new InternalError("Unknown walk type: " + walktype);230}231}232default void call(Env env, Call next, int total, int current, int markAt) {233if (current < total) {234next.call(env, next, total, current+1, markAt);235}236}237}238239public static class Marker implements Call {240final WalkType walkType;241Marker(WalkType walkType) {242this.walkType = walkType;243}244@Override245public WalkType getWalkType() {246return walkType;247}248249@Override250public void call(Env env, Call next, int total, int current, int markAt) {251env.markerCalled.incrementAndGet();252if (current < total) {253next.call(env, next, total, current+1, markAt);254} else {255next.walk(env);256}257}258}259260public static class Test implements Call {261final Marker marker;262final WalkType walkType;263final AtomicBoolean debug;264Test(WalkType walkType) {265this.walkType = walkType;266this.marker = new Marker(walkType);267this.debug = new AtomicBoolean();268}269@Override270public WalkType getWalkType() {271return walkType;272}273@Override274public void call(Env env, Call next, int total, int current, int markAt) {275if (current < total) {276int nexti = current + 1;277Call nextObj = nexti==markAt ? marker : next;278nextObj.call(env, next, total, nexti, markAt);279} else {280walk(env);281}282}283}284285public static Env runTest(Test test, int total, int markAt) {286Env env = new Env(total, markAt, test.debug);287test.call(env, test, total, 0, markAt);288return env;289}290291public static void checkTest(Env env, Test test) {292String threadName = Thread.currentThread().getName();293System.out.println(threadName + ": Marker called: " + env.markerCalled.get());294System.out.println(threadName + ": Max reached: " + env.maxReached.get());295System.out.println(threadName + ": Frames consumed: " + env.frameCounter.get());296if (env.markerCalled.get() == 0) {297throw new RuntimeException(Thread.currentThread().getName() + ": Marker was not called.");298}299if (env.markerCalled.get() > 1) {300throw new RuntimeException(Thread.currentThread().getName()301+ ": Marker was called more than once: " + env.maxReached.get());302}303if (!env.unexpected.isEmpty()) {304System.out.flush();305System.err.println("Encountered some unexpected infrastructure classes below 'main': "306+ env.unexpected);307}308if (env.maxReached.get() == 0) {309throw new RuntimeException(Thread.currentThread().getName()310+ ": max not reached");311}312if (env.maxReached.get() > 1) {313throw new RuntimeException(Thread.currentThread().getName()314+ ": max was reached more than once: " + env.maxReached.get());315}316}317318static class WalkThread extends Thread {319final static AtomicLong walkersCount = new AtomicLong();320Throwable failed = null;321final Test test;322public WalkThread(Test test) {323super("WalkThread[" + walkersCount.incrementAndGet() + ", type="324+ test.getWalkType() + "]");325this.test = test;326}327328public void run() {329try {330Env env = runTest(test, 1000, 10);331//waitWalkers(env);332checkTest(env, test);333} catch(Throwable t) {334failed = t;335}336}337}338339public static void main(String[] args) throws Throwable {340WalkThread[] threads = new WalkThread[Call.WalkType.values().length*3];341Throwable failed = null;342for (int i=0; i<threads.length; i++) {343Test test = new Test(Call.WalkType.values()[i%Call.WalkType.values().length]);344threads[i] = new WalkThread(test);345}346for (int i=0; i<threads.length; i++) {347threads[i].start();348}349for (int i=0; i<threads.length; i++) {350threads[i].join();351if (failed == null) failed = threads[i].failed;352else if (threads[i].failed == null) {353failed.addSuppressed(threads[i].failed);354}355}356if (failed != null) {357throw failed;358}359}360361}362363364