MultiThreadStackWalk.java revision 14176:8606d027b2c2
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
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            "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
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 {
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
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