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