1 /*
   2  * Copyright (c) 2013, 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 import java.lang.management.ThreadInfo;
  24 import java.lang.management.ThreadMXBean;
  25 import java.lang.Thread.State;
  26 import java.io.IOException;
  27 import java.lang.management.ManagementFactory;
  28 import java.util.logging.LogManager;
  29 import java.util.logging.Logger;
  30 import java.util.Map;
  31 
  32 /**
  33  * @test
  34  * @bug 8010939
  35  * @summary check for deadlock between findLogger() and drainLoggerRefQueueBounded()
  36  * @author jim.gish@oracle.com
  37  * @build DrainFindDeadlockTest
  38  * @run main/othervm/timeout=10 DrainFindDeadlockTest
  39  */
  40 
  41 /**
  42  * This test is checking for a deadlock between
  43  * LogManager$LoggerContext.findLogger() and
  44  * LogManager.drainLoggerRefQueueBounded() (which could happen by calling
  45  * Logger.getLogger() and LogManager.readConfiguration() in different threads)
  46  */
  47 public class DrainFindDeadlockTest {
  48     private LogManager mgr = LogManager.getLogManager();
  49     private final static int MAX_ITERATIONS = 100;
  50 
  51     // Get a ThreadMXBean so we can check for deadlock.  N.B. this may
  52     // not be supported on all platforms, which means we will have to
  53     // resort to the traditional test timeout method. However, if
  54     // we have the support we'll get the deadlock details if one
  55     // is detected.
  56     private final static ThreadMXBean threadMXBean =
  57             ManagementFactory.getThreadMXBean();
  58     private final boolean threadMXBeanDeadlockSupported =
  59             threadMXBean.isSynchronizerUsageSupported();
  60 
  61     private volatile boolean terminate = false;
  62     private volatile Exception failed = null;
  63 
  64     public static void main(String... args) throws IOException, Exception {
  65         new DrainFindDeadlockTest().testForDeadlock();
  66     }
  67 
  68     public static void randomDelay() {
  69         int runs = (int) Math.random() * 1000000;
  70         int c = 0;
  71 
  72         for (int i=0; i<runs; ++i) {
  73             c=c+i;
  74         }
  75     }
  76 
  77     public void fail(Exception failure) {
  78         if (failed == null) {
  79            failed = failure;
  80         } else {
  81            failure.printStackTrace();
  82         }
  83         terminate = true;
  84     }
  85 
  86     public void testForDeadlock() throws IOException, Exception {
  87         System.out.println("Deadlock detection "
  88                 + (threadMXBeanDeadlockSupported ? "is" : "is not") +
  89                             " available.");
  90         Thread setup = new Thread(new SetupLogger(), "SetupLogger");
  91         Thread readConfig = new Thread(new ReadConfig(), "ReadConfig");
  92         Thread check = new Thread(new DeadlockChecker(setup, readConfig),
  93                                    "DeadlockChecker");
  94 
  95         // make the threads daemon threads so they will go away when the
  96         // test exits
  97         setup.setDaemon(true);
  98         readConfig.setDaemon(true);
  99         check.setDaemon(true);
 100 
 101         check.start(); setup.start(); readConfig.start();
 102         try {
 103             check.join();
 104         } catch (InterruptedException ex) {
 105             ex.printStackTrace();
 106         }
 107 
 108         if (failed == null) {
 109             terminate = true;
 110             try {
 111                 readConfig.join();
 112                 setup.join();
 113             } catch (InterruptedException ex) {
 114                 ex.printStackTrace();
 115             }
 116             System.out.println("Test passed");
 117         } else {
 118             System.err.println("Test Failed: " + failed);
 119             throw failed;
 120         }
 121     }
 122 
 123     class SetupLogger implements Runnable {
 124         Logger logger = null;
 125 
 126         @Override
 127         public void run() {
 128             System.out.println("Running " + Thread.currentThread().getName());
 129             int i = 0;
 130             while (!terminate) {
 131                 logger = Logger.getLogger("DrainFindDeadlockTest"+(i++));
 132                 DrainFindDeadlockTest.randomDelay();
 133             }
 134         }
 135     }
 136 
 137     class ReadConfig implements Runnable {
 138         @Override
 139         public void run() {
 140             System.out.println("Running " + Thread.currentThread().getName());
 141             while (!terminate) {
 142                 try {
 143                     mgr.readConfiguration();
 144                 } catch (IOException | SecurityException ex) {
 145                     fail(new RuntimeException("FAILED: test setup problem", ex));
 146                 }
 147                 DrainFindDeadlockTest.randomDelay();
 148             }
 149         }
 150     }
 151 
 152     class DeadlockChecker implements Runnable {
 153         final Thread x, y;
 154         final long[] threadIds;
 155         DeadlockChecker(Thread t1, Thread t2) {
 156             this.x = t1;
 157             this.y = t2;
 158             threadIds = new long[] { t1.getId(), t2.getId() };
 159         }
 160 
 161         boolean threadsBlocked(ThreadInfo[] xyThreadInfos) {
 162             for (ThreadInfo threadInfo : xyThreadInfos) {
 163                 switch (threadInfo.getThreadState()) {
 164                     case BLOCKED:
 165                     case WAITING:
 166                     case TIMED_WAITING:
 167                         continue;
 168                     default:
 169                         System.out.println("Thread " + threadInfo.getThreadName() +
 170                                 " is not blocked: " + threadInfo.getThreadState());
 171                         return false;
 172                 }
 173             }
 174             return xyThreadInfos.length > 0;
 175         }
 176 
 177         long[] findDeadlockedThreads() {
 178             // It seems that calling ThreadMXBean.findDeadlockedThreads()
 179             // is not always reliable. Until that is fixed we will only
 180             // call it if we find that our two threads are simultaneously 
 181             // BLOCKED or WAITING.
 182             System.out.println("checking thread infos");
 183             ThreadInfo[] xyThreadInfos = threadMXBean.getThreadInfo(threadIds, true, true);
 184             if (!threadsBlocked(xyThreadInfos)) return null;
 185             /*
 186             if (xyThreadInfo != null && xyThreadInfos.length == 2) {
 187                 if (xyThreadInfo[0].getLockOwnerId() != xyThreadInfo[1].getThreadId()) {
 188                     return null;
 189                 } 
 190                 if (xyThreadInfo[1].getLockOwnerId() != xyThreadInfo[0].getThreadId()) {
 191                     return null;
 192                 }
 193             }
 194             */
 195             System.out.println("checking for deadlock");
 196             long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
 197             if (deadlockedThreads != null) {
 198                xyThreadInfos = threadMXBean.getThreadInfo(deadlockedThreads, true, true);
 199                if (!threadsBlocked(xyThreadInfos)) return null; 
 200             }
 201             return deadlockedThreads;
 202         }
 203 
 204         void checkState() {
 205             //            System.out.println("checkstate");
 206             boolean isXblocked = x.getState().equals(State.BLOCKED);
 207             boolean isYblocked = y.getState().equals(State.BLOCKED);
 208             long[] deadlockedThreads = null;
 209 
 210             if (isXblocked && isYblocked) {
 211                 System.out.println("threads blocked");
 212                 // they are both blocked, but this doesn't necessarily mean
 213                 // they are deadlocked
 214                 if (threadMXBeanDeadlockSupported) {
 215                     deadlockedThreads = findDeadlockedThreads();
 216                 } else {
 217                     System.out.println("Can't check for deadlock");
 218                 }
 219                 if (deadlockedThreads != null) {
 220                     System.out.println("We detected a deadlock! ");
 221                     ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(
 222                             deadlockedThreads, true, true);
 223                     for (ThreadInfo threadInfo: threadInfos) {
 224                         System.out.println("Thread " + threadInfo.getThreadName() + " blocked on " + 
 225                                    threadInfo.getLockName() + " owned by " + threadInfo.getLockOwnerName());
 226                     }
 227                     for (ThreadInfo threadInfo: threadInfos) {
 228                         System.out.println(threadInfo);
 229                     }
 230                     fail(new RuntimeException("TEST FAILED: Deadlock detected"));
 231                 } else if (!threadMXBeanDeadlockSupported) {
 232                     System.out.println("We may have a deadlock");
 233                     Map<Thread, StackTraceElement[]> threadMap =
 234                         Thread.getAllStackTraces();
 235                     dumpStack(threadMap.get(x), x);
 236                     dumpStack(threadMap.get(y), y);
 237                 }
 238             }
 239         }
 240 
 241         private void dumpStack(StackTraceElement[] aStackElt, Thread aThread) {
 242             if (aStackElt != null) {
 243                  System.out.println("Thread:" + aThread.getName() + ": " +
 244                                     aThread.getState());
 245                  for (StackTraceElement element: aStackElt) {
 246                     System.out.println("   " + element);
 247                  }
 248             }
 249         }
 250 
 251         @Override
 252         public void run() {
 253             System.out.println("Running " + Thread.currentThread().getName());
 254             for (int i=0; i < MAX_ITERATIONS*2; i++) {
 255                 if (terminate) return;
 256                 checkState();
 257                 try {
 258                     Thread.sleep(10);
 259                 } catch (InterruptedException ex) {
 260                 };
 261             }
 262         }
 263     }
 264 }