001/* 002 * (C) Copyright 2012 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * matic 016 */ 017package org.nuxeo.runtime.management.jvm; 018 019import java.io.File; 020import java.io.FileOutputStream; 021import java.io.IOException; 022import java.lang.management.LockInfo; 023import java.lang.management.ManagementFactory; 024import java.lang.management.MonitorInfo; 025import java.lang.management.ThreadInfo; 026import java.lang.management.ThreadMXBean; 027import java.util.HashMap; 028import java.util.Map; 029import java.util.Set; 030import java.util.Timer; 031import java.util.TimerTask; 032 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035 036public class ThreadDeadlocksDetector { 037 038 protected Timer timer; 039 040 protected final ThreadMXBean mgmt = ManagementFactory.getThreadMXBean(); 041 042 protected final Printer printer = new JVM16Printer(); 043 044 protected static final Log log = LogFactory.getLog(ThreadDeadlocksDetector.class); 045 046 public interface Printer { 047 048 void print(final StringBuilder sb, final ThreadInfo thread); 049 050 void printMonitors(final StringBuilder sb, final MonitorInfo[] monitors, final int index); 051 052 void printLock(StringBuilder sb, LockInfo lock); 053 054 } 055 056 public static class JVM16Printer implements Printer { 057 058 protected final ThreadMXBean mbean = ManagementFactory.getThreadMXBean(); 059 060 @Override 061 public void print(final StringBuilder sb, final ThreadInfo thread) { 062 MonitorInfo[] monitors = null; 063 if (mbean.isObjectMonitorUsageSupported()) { 064 monitors = thread.getLockedMonitors(); 065 } 066 sb.append("\n\"" + thread.getThreadName() + // NOI18N 067 "\" - Thread t@" + thread.getThreadId() + "\n"); // NOI18N 068 sb.append(" java.lang.Thread.State: " + thread.getThreadState()); // NOI18N 069 sb.append("\n"); // NOI18N 070 int index = 0; 071 for (StackTraceElement st : thread.getStackTrace()) { 072 LockInfo lock = thread.getLockInfo(); 073 String lockOwner = thread.getLockOwnerName(); 074 075 sb.append("\tat " + st.toString() + "\n"); // NOI18N 076 if (index == 0) { 077 if ("java.lang.Object".equals(st.getClassName()) && // NOI18N 078 "wait".equals(st.getMethodName())) { // NOI18N 079 if (lock != null) { 080 sb.append("\t- waiting on "); // NOI18N 081 printLock(sb, lock); 082 sb.append("\n"); // NOI18N 083 } 084 } else if (lock != null) { 085 if (lockOwner == null) { 086 sb.append("\t- parking to wait for "); // NOI18N 087 printLock(sb, lock); 088 sb.append("\n"); // NOI18N 089 } else { 090 sb.append("\t- waiting to lock "); // NOI18N 091 printLock(sb, lock); 092 sb.append(" owned by \"" + lockOwner + "\" t@" + thread.getLockOwnerId() + "\n"); // NOI18N 093 } 094 } 095 } 096 printMonitors(sb, monitors, index); 097 index++; 098 } 099 StringBuilder jnisb = new StringBuilder(); 100 printMonitors(jnisb, monitors, -1); 101 if (jnisb.length() > 0) { 102 sb.append(" JNI locked monitors:\n"); 103 sb.append(jnisb); 104 } 105 if (mbean.isSynchronizerUsageSupported()) { 106 sb.append("\n Locked ownable synchronizers:"); // NOI18N 107 LockInfo[] synchronizers = thread.getLockedSynchronizers(); 108 if (synchronizers == null || synchronizers.length == 0) { 109 sb.append("\n\t- None\n"); // NOI18N 110 } else { 111 for (LockInfo li : synchronizers) { 112 sb.append("\n\t- locked "); // NOI18N 113 printLock(sb, li); 114 sb.append("\n"); // NOI18N 115 } 116 } 117 } 118 } 119 120 @Override 121 public void printMonitors(final StringBuilder sb, final MonitorInfo[] monitors, final int index) { 122 if (monitors != null) { 123 for (MonitorInfo mi : monitors) { 124 if (mi.getLockedStackDepth() == index) { 125 sb.append("\t- locked "); // NOI18N 126 printLock(sb, mi); 127 sb.append("\n"); // NOI18N 128 } 129 } 130 } 131 } 132 133 @Override 134 public void printLock(StringBuilder sb, LockInfo lock) { 135 String id = Integer.toHexString(lock.getIdentityHashCode()); 136 String className = lock.getClassName(); 137 138 sb.append("<" + id + "> (a " + className + ")"); // NOI18N 139 } 140 141 } 142 143 public interface Listener { 144 145 void deadlockDetected(long[] ids, File dumpFile); 146 147 } 148 149 public static class KillListener implements Listener { 150 151 @Override 152 public void deadlockDetected(long[] ids, File dumpFile) { 153 log.error("Exiting, detected threads dead locks, see thread dump in " + dumpFile.getPath()); 154 System.exit(1); 155 } 156 157 } 158 159 public File dump(long[] lockedIds) throws IOException { 160 File file = File.createTempFile("tcheck-", ".tdump", new File("target")); 161 FileOutputStream os = new FileOutputStream(file); 162 ThreadInfo[] infos = mgmt.dumpAllThreads(true, true); 163 try { 164 for (ThreadInfo info : infos) { 165 StringBuilder sb = new StringBuilder(); 166 printer.print(sb, info); 167 os.write(sb.toString().getBytes("UTF-8")); 168 } 169 StringBuilder sb = new StringBuilder(); 170 sb.append("Locked threads: "); 171 172 String comma = ""; 173 for (long lockedId : lockedIds) { 174 sb.append(comma).append(lockedId); 175 comma = ","; 176 } 177 os.write(sb.toString().getBytes("UTF-8")); 178 } finally { 179 os.close(); 180 } 181 return file; 182 } 183 184 public long[] detectThreadLock() { 185 long[] findMonitorDeadlockedThreads = mgmt.findMonitorDeadlockedThreads(); 186 if (findMonitorDeadlockedThreads == null) { 187 return new long[0]; 188 } 189 return findMonitorDeadlockedThreads; 190 } 191 192 protected class Task extends TimerTask { 193 194 protected final Listener listener; 195 196 protected Task(Listener listener) { 197 this.listener = listener; 198 } 199 200 @Override 201 public void run() { 202 long[] ids = detectThreadLock(); 203 if (ids.length == 0) { 204 return; 205 } 206 File dumpFile; 207 try { 208 dumpFile = dump(ids); 209 } catch (IOException e) { 210 log.error("Cannot dump threads", e); 211 dumpFile = new File("/dev/null"); 212 } 213 listener.deadlockDetected(ids, dumpFile); 214 } 215 216 } 217 218 public void schedule(long period, Listener listener) { 219 if (timer != null) { 220 throw new IllegalStateException("timer already scheduled"); 221 } 222 timer = new Timer("Thread Deadlocks Detector"); 223 timer.schedule(new Task(listener), 1000, period); 224 } 225 226 public void cancel() { 227 if (timer == null) { 228 throw new IllegalStateException("timer not scheduled"); 229 } 230 timer.cancel(); 231 timer = null; 232 } 233 234 @SuppressWarnings("deprecation") 235 public static void killThreads(Set<Long> ids) { 236 Map<Long, Thread> threads = getThreads(); 237 for (long id : ids) { 238 Thread thread = threads.get(id); 239 if (thread == null) { 240 continue; 241 } 242 thread.stop(); 243 } 244 } 245 246 protected static Map<Long, Thread> getThreads() { 247 ThreadGroup root = rootGroup(Thread.currentThread().getThreadGroup()); 248 int nThreads = root.activeCount(); 249 Thread[] threads = new Thread[2 * nThreads]; 250 root.enumerate(threads); 251 Map<Long, Thread> map = new HashMap<Long, Thread>(threads.length); 252 for (Thread thread : threads) { 253 if (thread == null) { 254 continue; 255 } 256 map.put(thread.getId(), thread); 257 } 258 return map; 259 } 260 261 protected static ThreadGroup rootGroup(ThreadGroup group) { 262 ThreadGroup parent = group.getParent(); 263 if (parent == null) { 264 return group; 265 } 266 return rootGroup(parent); 267 } 268 269}