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