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 import java.util.Arrays; 25 import java.util.Collections; 26 import java.util.List; 27 import java.util.Set; 28 import java.util.TreeSet; 29 import java.util.concurrent.atomic.AtomicBoolean; 30 import java.util.concurrent.atomic.AtomicLong; 31 import java.lang.StackWalker.StackFrame; 32 import 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 */ 46 public 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 classForName(String name)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 isStreamPipeline(Class<?> clazz)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 Env(long total, long markAt, AtomicBoolean debug)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. Env(Env orig, long start)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. consume(StackFrame sfi)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 } getWalkType()219 default WalkType getWalkType() { return WalkType.WALKSTACK;} walk(Env env)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 } call(Env env, Call next, int total, int current, int markAt)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; Marker(WalkType walkType)242 Marker(WalkType walkType) { 243 this.walkType = walkType; 244 } 245 @Override getWalkType()246 public WalkType getWalkType() { 247 return walkType; 248 } 249 250 @Override call(Env env, Call next, int total, int current, int markAt)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; Test(WalkType walkType)265 Test(WalkType walkType) { 266 this.walkType = walkType; 267 this.marker = new Marker(walkType); 268 this.debug = new AtomicBoolean(); 269 } 270 @Override getWalkType()271 public WalkType getWalkType() { 272 return walkType; 273 } 274 @Override call(Env env, Call next, int total, int current, int markAt)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 runTest(Test test, int total, int markAt)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 checkTest(Env env, Test test)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; WalkThread(Test test)323 public WalkThread(Test test) { 324 super("WalkThread[" + walkersCount.incrementAndGet() + ", type=" 325 + test.getWalkType() + "]"); 326 this.test = test; 327 } 328 run()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 main(String[] args)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