1 /* 2 * Copyright (c) 2006, 2017, 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 /* 25 * @test 26 * @bug 6467152 6716076 6829503 8132550 27 * @summary deadlock occurs in LogManager initialization and JVM termination 28 * @author Serguei Spitsyn / Hitachi / Martin Buchholz 29 * 30 * @build LoggingDeadlock2 31 * @run main LoggingDeadlock2 32 * @key randomness 33 */ 34 35 /* 36 * 37 * There is a clear deadlock between LogManager.<clinit> and 38 * Cleaner.run() methods. 39 * T1 thread: 40 * The LogManager.<clinit> creates LogManager.manager object, 41 * sets shutdown hook with the Cleaner class and then waits 42 * to lock the LogManager.manager monitor. 43 * T2 thread: 44 * It is started by the System.exit() as shutdown hook thread. 45 * It locks the LogManager.manager monitor and then calls the 46 * static methods of the LogManager class (in this particular 47 * case it is a trick of the inner classes implementation). 48 * It is waits when the LogManager.<clinit> is completed. 49 * 50 * This is a regression test for this bug. 51 */ 52 53 import java.util.Arrays; 54 import java.util.List; 55 import java.util.Random; 56 import java.util.concurrent.CyclicBarrier; 57 import java.util.concurrent.atomic.AtomicInteger; 58 import java.util.logging.LogManager; 59 import java.io.File; 60 import java.io.IOException; 61 import java.io.InputStream; 62 import java.io.InputStreamReader; 63 import java.io.Reader; 64 import java.util.concurrent.TimeUnit; 65 66 public class LoggingDeadlock2 { 67 68 // ask child process to dumpstack after 60secs 69 public static final long DUMP_STACK_FREQUENCY_MS = 60000; 70 71 // A marker that allows to validate the subprocess output. 72 public static final String MARKER = "$"; 73 realMain(String arg[])74 public static void realMain(String arg[]) throws Throwable { 75 try { 76 System.out.println(javaChildArgs); 77 ProcessBuilder pb = new ProcessBuilder(javaChildArgs); 78 ProcessResults r = run(pb.start()); 79 equal(r.exitValue(), 99); 80 81 // output of subprocess should end with "$" 82 final String out = r.out(); 83 final String trailingOutput = out.indexOf(MARKER) > -1 84 ? out.substring(out.indexOf(MARKER)+MARKER.length()) 85 : out; 86 equal(trailingOutput, ""); 87 equal(r.err(), ""); 88 equal(out.startsWith("JavaChild started"), true); 89 equal(out.endsWith("$"), true); 90 } catch (Throwable t) { unexpected(t); } 91 } 92 93 public static class JavaChild { main(String args[])94 public static void main(String args[]) throws Throwable { 95 System.out.println("JavaChild started"); 96 97 final CyclicBarrier startingGate = new CyclicBarrier(2); 98 final Throwable[] thrown = new Throwable[1]; 99 100 // Some random variation, to help tickle races. 101 final Random rnd = new Random(); 102 final long seed = rnd.nextLong(); 103 rnd.setSeed(seed); 104 System.out.println("seed=" + seed); 105 final boolean dojoin = rnd.nextBoolean(); 106 final int JITTER = 1024; 107 final int iters1 = rnd.nextInt(JITTER); 108 final int iters2 = JITTER - iters1; 109 final AtomicInteger counter = new AtomicInteger(0); 110 System.out.println("dojoin=" + dojoin); 111 System.out.println("iters1=" + iters1); 112 System.out.println("iters2=" + iters2); 113 114 Thread exiter = new Thread() { 115 public void run() { 116 try { 117 startingGate.await(); 118 for (int i = 0; i < iters1; i++) 119 counter.getAndIncrement(); 120 System.exit(99); 121 } catch (Throwable t) { 122 t.printStackTrace(); 123 System.exit(86); 124 } 125 }}; 126 exiter.start(); 127 128 System.out.println("exiter started"); 129 130 // signal end of verbose output 131 System.out.print(MARKER); 132 System.out.flush(); 133 134 startingGate.await(); 135 for (int i = 0; i < iters2; i++) 136 counter.getAndIncrement(); 137 // This may or may not result in a first call to 138 // Runtime.addShutdownHook after shutdown has already 139 // commenced. 140 LogManager.getLogManager(); 141 142 if (dojoin) { 143 exiter.join(); 144 if (thrown[0] != null) 145 throw new Error(thrown[0]); 146 check(counter.get() == JITTER); 147 } 148 } 149 } 150 151 //---------------------------------------------------------------- 152 // The rest of this test is copied from ProcessBuilder/Basic.java 153 //---------------------------------------------------------------- 154 private static final String javaExe = 155 System.getProperty("java.home") + 156 File.separator + "bin" + File.separator + "java"; 157 private static final String jstackExe = 158 System.getProperty("java.home") + 159 File.separator + "bin" + File.separator + "jstack"; 160 161 private static final String classpath = 162 System.getProperty("java.class.path"); 163 164 private static final List<String> javaChildArgs = 165 Arrays.asList(new String[] 166 { javaExe, "-classpath", classpath, 167 "LoggingDeadlock2$JavaChild"}); 168 169 private static class ProcessResults { 170 private final String out; 171 private final String err; 172 private final int exitValue; 173 private final Throwable throwable; 174 ProcessResults(String out, String err, int exitValue, Throwable throwable)175 public ProcessResults(String out, 176 String err, 177 int exitValue, 178 Throwable throwable) { 179 this.out = out; 180 this.err = err; 181 this.exitValue = exitValue; 182 this.throwable = throwable; 183 } 184 out()185 public String out() { return out; } err()186 public String err() { return err; } exitValue()187 public int exitValue() { return exitValue; } 188 toString()189 public String toString() { 190 StringBuilder sb = new StringBuilder(); 191 sb.append("<STDOUT>\n" + out() + "</STDOUT>\n") 192 .append("<STDERR>\n" + err() + "</STDERR>\n") 193 .append("exitValue = " + exitValue + "\n"); 194 if (throwable != null) 195 sb.append(throwable.getStackTrace()); 196 return sb.toString(); 197 } 198 } 199 200 private static class StreamAccumulator extends Thread { 201 private final InputStream is; 202 private final StringBuilder sb = new StringBuilder(); 203 private Throwable throwable = null; 204 result()205 public String result () throws Throwable { 206 if (throwable != null) 207 throw throwable; 208 return sb.toString(); 209 } 210 StreamAccumulator(InputStream is)211 StreamAccumulator (InputStream is) { 212 this.is = is; 213 } 214 run()215 public void run() { 216 try { 217 Reader r = new InputStreamReader(is); 218 int n; 219 while ((n = r.read()) > 0) { 220 sb.append((char)n); 221 222 // prints everything immediately to System.out so that we can 223 // see the traces even in the event of a test timeout 224 System.out.write((char)n); 225 System.out.flush(); 226 227 } 228 } catch (Throwable t) { 229 throwable = t; 230 } finally { 231 try { is.close(); } 232 catch (Throwable t) { throwable = t; } 233 } 234 } 235 } 236 237 /** 238 * If the child process deadlocks, then the parent may fail in timeout. 239 * In that case, we won't have any interesting traces, unless we manage 240 * to get a thread dump from the child. 241 * It is unsure whether obtaining a thread dump from a deadlocked child 242 * will work - but maybe we could see something if the timeout is a false 243 * positive (the child has not deadlocked but hasn't managed to fully start 244 * yet, for instance). 245 * The idea here is to periodically try to obtain a thread dump from the 246 * child, every 60sec - which should be always less than the jtreg timeout. 247 */ 248 private static class TimeoutThread extends Thread { 249 final long ms; 250 final Process process; TimeoutThread(long ms, Process p)251 TimeoutThread(long ms, Process p) { 252 super("TimeoutThread"); 253 setDaemon(true); 254 this.ms = ms; 255 this.process = p; 256 } 257 258 @Override run()259 public void run() { 260 long start = System.nanoTime(); 261 try { 262 while (true) { 263 sleep(ms); 264 System.err.println("Timeout reached: " + ms); 265 if (process.isAlive()) { 266 long pid = process.pid(); 267 ProcessBuilder jstack = new ProcessBuilder(jstackExe, String.valueOf(pid)); 268 System.err.println("Dumping subprocess stack: " + pid); 269 Process p = jstack.inheritIO().start(); 270 p.waitFor(ms, TimeUnit.MILLISECONDS); 271 } else { 272 System.err.println("Process is not alive!"); 273 break; 274 } 275 } 276 } catch (InterruptedException ex) { 277 System.err.println("Interrupted: " + ex); 278 } catch (IOException io) { 279 System.err.println("Failed to get stack from subprocess"); 280 io.printStackTrace(); 281 } 282 } 283 284 285 } 286 run(Process p)287 private static ProcessResults run(Process p) { 288 Throwable throwable = null; 289 int exitValue = -1; 290 String out = ""; 291 String err = ""; 292 293 StreamAccumulator outAccumulator = 294 new StreamAccumulator(p.getInputStream()); 295 StreamAccumulator errAccumulator = 296 new StreamAccumulator(p.getErrorStream()); 297 298 try { 299 System.out.println("Waiting for child process to exit"); 300 outAccumulator.start(); 301 errAccumulator.start(); 302 303 // ask subprocess to dump stack every 60 secs. 304 new TimeoutThread(DUMP_STACK_FREQUENCY_MS, p).start(); 305 306 exitValue = p.waitFor(); 307 System.out.println("\nChild exited with status: " + exitValue); 308 309 outAccumulator.join(); 310 errAccumulator.join(); 311 312 out = outAccumulator.result(); 313 err = errAccumulator.result(); 314 } catch (Throwable t) { 315 throwable = t; 316 } 317 318 return new ProcessResults(out, err, exitValue, throwable); 319 } 320 321 //--------------------- Infrastructure --------------------------- 322 static volatile int passed = 0, failed = 0; pass()323 static void pass() {passed++;} fail()324 static void fail() {failed++; Thread.dumpStack();} fail(String msg)325 static void fail(String msg) {System.out.println(msg); fail();} unexpected(Throwable t)326 static void unexpected(Throwable t) {failed++; t.printStackTrace();} check(boolean cond)327 static void check(boolean cond) {if (cond) pass(); else fail();} check(boolean cond, String m)328 static void check(boolean cond, String m) {if (cond) pass(); else fail(m);} equal(Object x, Object y)329 static void equal(Object x, Object y) { 330 if (x == null ? y == null : x.equals(y)) pass(); 331 else fail(x + " not equal to " + y);} main(String[] args)332 public static void main(String[] args) throws Throwable { 333 try {realMain(args);} catch (Throwable t) {unexpected(t);} 334 System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); 335 if (failed > 0) throw new AssertionError("Some tests failed");} 336 } 337