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 package test.java.lang.StackWalker; 25 26 import java.util.Arrays; 27 import java.util.Collections; 28 import java.util.List; 29 import java.util.Set; 30 import java.util.TreeSet; 31 import java.util.concurrent.atomic.AtomicBoolean; 32 import java.util.concurrent.atomic.AtomicLong; 33 import java.lang.StackWalker.StackFrame; 34 35 import static java.lang.StackWalker.Option.*; 36 37 38 /** 39 * @test 40 * @bug 8140450 41 * @summary This test will walk the stack using different methods, called 42 * from several threads running concurrently. 43 * Except in the case of MTSTACKSTREAM - which takes a snapshot 44 * of the stack before walking, all the methods only allow to 45 * walk the current thread stack. 46 * @run main/othervm MultiThreadStackWalk 47 * @author danielfuchs 48 */ 49 public class MultiThreadStackWalk { 50 51 static Set<String> infrastructureClasses = new TreeSet<>(Arrays.asList( 52 "jdk.internal.reflect.NativeMethodAccessorImpl", 53 "jdk.internal.reflect.DelegatingMethodAccessorImpl", 54 "java.lang.reflect.Method", 55 "com.sun.javatest.regtest.MainWrapper$MainThread", 56 "java.lang.Thread" 57 )); 58 59 60 static final List<Class<?>> streamPipelines = Arrays.asList( 61 classForName("java.util.stream.AbstractPipeline"), 62 classForName("java.util.stream.TerminalOp") 63 ); 64 classForName(String name)65 static Class<?> classForName(String name) { 66 try { 67 return Class.forName(name); 68 } catch (ClassNotFoundException e){ 69 throw new RuntimeException(e); 70 } 71 } 72 isStreamPipeline(Class<?> clazz)73 private static boolean isStreamPipeline(Class<?> clazz) { 74 for (Class<?> c : streamPipelines) { 75 if (c.isAssignableFrom(clazz)) { 76 return true; 77 } 78 } 79 return false; 80 } 81 82 /** 83 * An object that contains variables pertaining to the execution 84 * of the test within one thread. 85 * A small amount of those variable are shared with sub threads when 86 * the stack walk is executed in parallel - that is when spliterators 87 * obtained from trySplit are handed over to an instance of SplitThread 88 * in order to parallelize thread walking. 89 * @see WalkThread#handOff(MultiThreadStackWalk.Env, java.util.Spliterator, boolean, boolean) 90 * @see Env#split(MultiThreadStackWalk.Env) 91 */ 92 public static class Env { 93 final AtomicLong frameCounter; // private: the counter for the current thread. 94 final long checkMarkAt; // constant: the point at which we expect to 95 // find the marker in consume() 96 final long max; // constant: the maximum number of recursive 97 // calls to Call. 98 final AtomicBoolean debug ; // shared: whether debug is active for the 99 // instance of Test from which this instance 100 // of Env was spawned 101 final AtomicLong markerCalled; // shared: whether the marker was reached 102 final AtomicLong maxReached; // shared: whether max was reached 103 final Set<String> unexpected; // shared: list of unexpected infrastructure 104 // classes encountered after max is reached 105 Env(long total, long markAt, AtomicBoolean debug)106 public Env(long total, long markAt, AtomicBoolean debug) { 107 this.debug = debug; 108 frameCounter = new AtomicLong(); 109 maxReached = new AtomicLong(); 110 unexpected = Collections.synchronizedSet(new TreeSet<>()); 111 this.max = total+2; 112 this.checkMarkAt = total - markAt + 1; 113 this.markerCalled = new AtomicLong(); 114 } 115 116 // Used when delegating part of the stack walking to a sub thread 117 // see WalkThread.handOff. Env(Env orig, long start)118 private Env(Env orig, long start) { 119 debug = orig.debug; 120 frameCounter = new AtomicLong(start); 121 maxReached = orig.maxReached; 122 unexpected = orig.unexpected; 123 max = orig.max; 124 checkMarkAt = orig.checkMarkAt; 125 markerCalled = orig.markerCalled; 126 } 127 128 // The stack walk consumer method, where all the checks are 129 // performed. consume(StackFrame sfi)130 public void consume(StackFrame sfi) { 131 if (frameCounter.get() == 0 && isStreamPipeline(sfi.getDeclaringClass())) { 132 return; 133 } 134 135 final long count = frameCounter.getAndIncrement(); 136 final StringBuilder builder = new StringBuilder(); 137 builder.append("Declaring class[") 138 .append(count) 139 .append("]: ") 140 .append(sfi.getDeclaringClass()); 141 builder.append('\n'); 142 builder.append("\t") 143 .append(sfi.getClassName()) 144 .append(".") 145 .append(sfi.toStackTraceElement().getMethodName()) 146 .append(sfi.toStackTraceElement().isNativeMethod() 147 ? "(native)" 148 : "(" + sfi.toStackTraceElement().getFileName() 149 +":"+sfi.toStackTraceElement().getLineNumber()+")"); 150 builder.append('\n'); 151 if (debug.get()) { 152 System.out.print("[debug] " + builder.toString()); 153 builder.setLength(0); 154 } 155 if (count == max) { 156 maxReached.incrementAndGet(); 157 } 158 if (count == checkMarkAt) { 159 if (sfi.getDeclaringClass() != MultiThreadStackWalk.Marker.class) { 160 throw new RuntimeException("Expected Marker at " + count 161 + ", found " + sfi.getDeclaringClass()); 162 } 163 } else { 164 if (count <= 0 && sfi.getDeclaringClass() != MultiThreadStackWalk.Call.class) { 165 throw new RuntimeException("Expected Call at " + count 166 + ", found " + sfi.getDeclaringClass()); 167 } else if (count > 0 && count < max && sfi.getDeclaringClass() != MultiThreadStackWalk.Test.class) { 168 throw new RuntimeException("Expected Test at " + count 169 + ", found " + sfi.getDeclaringClass()); 170 } else if (count == max && sfi.getDeclaringClass() != MultiThreadStackWalk.class) { 171 throw new RuntimeException("Expected MultiThreadStackWalk at " 172 + count + ", found " + sfi.getDeclaringClass()); 173 } else if (count == max && !sfi.toStackTraceElement().getMethodName().equals("runTest")) { 174 throw new RuntimeException("Expected runTest method at " 175 + count + ", found " + sfi.toStackTraceElement().getMethodName()); 176 } else if (count == max+1) { 177 if (sfi.getDeclaringClass() != MultiThreadStackWalk.WalkThread.class) { 178 throw new RuntimeException("Expected MultiThreadStackWalk at " 179 + count + ", found " + sfi.getDeclaringClass()); 180 } 181 if (count == max && !sfi.toStackTraceElement().getMethodName().equals("run")) { 182 throw new RuntimeException("Expected main method at " 183 + count + ", found " + sfi.toStackTraceElement().getMethodName()); 184 } 185 } else if (count > max+1) { 186 // expect JTreg infrastructure... 187 if (!infrastructureClasses.contains(sfi.getDeclaringClass().getName())) { 188 System.err.println("**** WARNING: encountered unexpected infrastructure class at " 189 + count +": " + sfi.getDeclaringClass().getName()); 190 unexpected.add(sfi.getDeclaringClass().getName()); 191 } 192 } 193 } 194 if (count == 100) { 195 // Maybe we should had some kind of checking inside that lambda 196 // too. For the moment we should be satisfied if it doesn't throw 197 // any exception and doesn't make the outer walk fail... 198 StackWalker.getInstance(RETAIN_CLASS_REFERENCE).forEach(x -> { 199 StackTraceElement st = x.toStackTraceElement(); 200 StringBuilder b = new StringBuilder(); 201 b.append("*** inner walk: ") 202 .append(x.getClassName()) 203 .append(st == null ? "- no stack trace element -" : 204 ("." + st.getMethodName() 205 + (st.isNativeMethod() ? "(native)" : 206 "(" + st.getFileName() 207 + ":" + st.getLineNumber() + ")"))) 208 .append('\n'); 209 if (debug.get()) { 210 System.out.print(b.toString()); 211 b.setLength(0); 212 } 213 }); 214 } 215 } 216 } 217 218 public interface Call { 219 enum WalkType { 220 WALKSTACK, // use Thread.walkStack 221 } getWalkType()222 default WalkType getWalkType() { return WalkType.WALKSTACK;} walk(Env env)223 default void walk(Env env) { 224 WalkType walktype = getWalkType(); 225 System.out.println("Thread "+ Thread.currentThread().getName() 226 +" starting walk with " + walktype); 227 switch(walktype) { 228 case WALKSTACK: 229 StackWalker.getInstance(RETAIN_CLASS_REFERENCE) 230 .forEach(env::consume); 231 break; 232 default: 233 throw new InternalError("Unknown walk type: " + walktype); 234 } 235 } call(Env env, Call next, int total, int current, int markAt)236 default void call(Env env, Call next, int total, int current, int markAt) { 237 if (current < total) { 238 next.call(env, next, total, current+1, markAt); 239 } 240 } 241 } 242 243 public static class Marker implements Call { 244 final WalkType walkType; Marker(WalkType walkType)245 Marker(WalkType walkType) { 246 this.walkType = walkType; 247 } 248 @Override getWalkType()249 public WalkType getWalkType() { 250 return walkType; 251 } 252 253 @Override call(Env env, Call next, int total, int current, int markAt)254 public void call(Env env, Call next, int total, int current, int markAt) { 255 env.markerCalled.incrementAndGet(); 256 if (current < total) { 257 next.call(env, next, total, current+1, markAt); 258 } else { 259 next.walk(env); 260 } 261 } 262 } 263 264 public static class Test implements Call { 265 final Marker marker; 266 final WalkType walkType; 267 final AtomicBoolean debug; Test(WalkType walkType)268 Test(WalkType walkType) { 269 this.walkType = walkType; 270 this.marker = new Marker(walkType); 271 this.debug = new AtomicBoolean(); 272 } 273 @Override getWalkType()274 public WalkType getWalkType() { 275 return walkType; 276 } 277 @Override call(Env env, Call next, int total, int current, int markAt)278 public void call(Env env, Call next, int total, int current, int markAt) { 279 if (current < total) { 280 int nexti = current + 1; 281 Call nextObj = nexti==markAt ? marker : next; 282 nextObj.call(env, next, total, nexti, markAt); 283 } else { 284 walk(env); 285 } 286 } 287 } 288 runTest(Test test, int total, int markAt)289 public static Env runTest(Test test, int total, int markAt) { 290 Env env = new Env(total, markAt, test.debug); 291 test.call(env, test, total, 0, markAt); 292 return env; 293 } 294 checkTest(Env env, Test test)295 public static void checkTest(Env env, Test test) { 296 String threadName = Thread.currentThread().getName(); 297 System.out.println(threadName + ": Marker called: " + env.markerCalled.get()); 298 System.out.println(threadName + ": Max reached: " + env.maxReached.get()); 299 System.out.println(threadName + ": Frames consumed: " + env.frameCounter.get()); 300 if (env.markerCalled.get() == 0) { 301 throw new RuntimeException(Thread.currentThread().getName() + ": Marker was not called."); 302 } 303 if (env.markerCalled.get() > 1) { 304 throw new RuntimeException(Thread.currentThread().getName() 305 + ": Marker was called more than once: " + env.maxReached.get()); 306 } 307 if (!env.unexpected.isEmpty()) { 308 System.out.flush(); 309 System.err.println("Encountered some unexpected infrastructure classes below 'main': " 310 + env.unexpected); 311 } 312 if (env.maxReached.get() == 0) { 313 throw new RuntimeException(Thread.currentThread().getName() 314 + ": max not reached"); 315 } 316 if (env.maxReached.get() > 1) { 317 throw new RuntimeException(Thread.currentThread().getName() 318 + ": max was reached more than once: " + env.maxReached.get()); 319 } 320 } 321 322 static class WalkThread extends Thread { 323 final static AtomicLong walkersCount = new AtomicLong(); 324 Throwable failed = null; 325 final Test test; WalkThread(Test test)326 public WalkThread(Test test) { 327 super("WalkThread[" + walkersCount.incrementAndGet() + ", type=" 328 + test.getWalkType() + "]"); 329 this.test = test; 330 } 331 run()332 public void run() { 333 try { 334 // Android-changed: Avoid StackOverflowError for too many recursions. 335 // Env env = runTest(test, 1000, 10); 336 Env env = runTest(test, 200, 10); 337 //waitWalkers(env); 338 checkTest(env, test); 339 } catch(Throwable t) { 340 failed = t; 341 } 342 } 343 } 344 main(String[] args)345 public static void main(String[] args) throws Throwable { 346 WalkThread[] threads = new WalkThread[Call.WalkType.values().length*3]; 347 Throwable failed = null; 348 for (int i=0; i<threads.length; i++) { 349 Test test = new Test(Call.WalkType.values()[i%Call.WalkType.values().length]); 350 threads[i] = new WalkThread(test); 351 } 352 for (int i=0; i<threads.length; i++) { 353 threads[i].start(); 354 } 355 for (int i=0; i<threads.length; i++) { 356 threads[i].join(); 357 if (failed == null) failed = threads[i].failed; 358 else if (threads[i].failed == null) { 359 failed.addSuppressed(threads[i].failed); 360 } 361 } 362 if (failed != null) { 363 throw failed; 364 } 365 } 366 367 } 368