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\"") 069 .append(thread.getThreadName()) 070 .append("\" - Thread t@") 071 .append(thread.getThreadId()) 072 .append("\n") 073 .append(" java.lang.Thread.State: ") 074 .append(thread.getThreadState()) 075 .append("\n"); 076 int index = 0; 077 for (StackTraceElement st : thread.getStackTrace()) { 078 LockInfo lock = thread.getLockInfo(); 079 String lockOwner = thread.getLockOwnerName(); 080 081 sb.append("\tat ").append(st.toString()).append("\n"); 082 if (index == 0) { 083 if ("java.lang.Object".equals(st.getClassName()) && 084 "wait".equals(st.getMethodName())) { 085 if (lock != null) { 086 sb.append("\t- waiting on "); 087 printLock(sb, lock); 088 sb.append("\n"); 089 } 090 } else if (lock != null) { 091 if (lockOwner == null) { 092 sb.append("\t- parking to wait for "); 093 printLock(sb, lock); 094 sb.append("\n"); 095 } else { 096 sb.append("\t- waiting to lock "); 097 printLock(sb, lock); 098 sb.append(" owned by \"") 099 .append(lockOwner) 100 .append("\" t@") 101 .append(thread.getLockOwnerId()) 102 .append("\n"); 103 } 104 } 105 } 106 printMonitors(sb, monitors, index); 107 index++; 108 } 109 StringBuilder jnisb = new StringBuilder(); 110 printMonitors(jnisb, monitors, -1); 111 if (jnisb.length() > 0) { 112 sb.append(" JNI locked monitors:\n"); 113 sb.append(jnisb); 114 } 115 if (mbean.isSynchronizerUsageSupported()) { 116 sb.append("\n Locked ownable synchronizers:"); 117 LockInfo[] synchronizers = thread.getLockedSynchronizers(); 118 if (synchronizers == null || synchronizers.length == 0) { 119 sb.append("\n\t- None\n"); 120 } else { 121 for (LockInfo li : synchronizers) { 122 sb.append("\n\t- locked "); 123 printLock(sb, li); 124 sb.append("\n"); 125 } 126 } 127 } 128 } 129 130 @Override 131 public void printMonitors(final StringBuilder sb, final MonitorInfo[] monitors, final int index) { 132 if (monitors != null) { 133 for (MonitorInfo mi : monitors) { 134 if (mi.getLockedStackDepth() == index) { 135 sb.append("\t- locked "); 136 printLock(sb, mi); 137 sb.append("\n"); 138 } 139 } 140 } 141 } 142 143 @Override 144 public void printLock(StringBuilder sb, LockInfo lock) { 145 String id = Integer.toHexString(lock.getIdentityHashCode()); 146 String className = lock.getClassName(); 147 sb.append("<").append(id).append("> (a ").append(className).append(")"); 148 } 149 150 } 151 152 public interface Listener { 153 154 void deadlockDetected(long[] ids, File dumpFile); 155 156 } 157 158 public static class KillListener implements Listener { 159 160 @Override 161 public void deadlockDetected(long[] ids, File dumpFile) { 162 log.error("Exiting, detected threads dead locks, see thread dump in " + dumpFile.getPath()); 163 System.exit(1); 164 } 165 166 } 167 168 public File dump(long[] lockedIds) throws IOException { 169 File file = File.createTempFile("tdump-", ".log", new File("target")); 170 FileOutputStream os = new FileOutputStream(file); 171 ThreadInfo[] infos = mgmt.dumpAllThreads(true, true); 172 try { 173 for (ThreadInfo info : infos) { 174 StringBuilder sb = new StringBuilder(); 175 printer.print(sb, info); 176 os.write(sb.toString().getBytes("UTF-8")); 177 } 178 StringBuilder sb = new StringBuilder(); 179 sb.append("Locked threads: "); 180 181 String comma = ""; 182 for (long lockedId : lockedIds) { 183 sb.append(comma).append(lockedId); 184 comma = ","; 185 } 186 os.write(sb.toString().getBytes("UTF-8")); 187 } finally { 188 os.close(); 189 } 190 return file; 191 } 192 193 public long[] detectThreadLock() { 194 long[] findMonitorDeadlockedThreads = mgmt.findMonitorDeadlockedThreads(); 195 if (findMonitorDeadlockedThreads == null) { 196 return new long[0]; 197 } 198 return findMonitorDeadlockedThreads; 199 } 200 201 protected class Task extends TimerTask { 202 203 protected final Listener listener; 204 205 protected Task(Listener listener) { 206 this.listener = listener; 207 } 208 209 @Override 210 public void run() { 211 long[] ids = detectThreadLock(); 212 if (ids.length == 0) { 213 return; 214 } 215 File dumpFile; 216 try { 217 dumpFile = dump(ids); 218 } catch (IOException e) { 219 log.error("Cannot dump threads", e); 220 dumpFile = new File("/dev/null"); 221 } 222 listener.deadlockDetected(ids, dumpFile); 223 } 224 225 } 226 227 public void schedule(long period, Listener listener) { 228 if (timer != null) { 229 throw new IllegalStateException("timer already scheduled"); 230 } 231 timer = new Timer("Thread Deadlocks Detector"); 232 timer.schedule(new Task(listener), 1000, period); 233 } 234 235 public void cancel() { 236 if (timer == null) { 237 throw new IllegalStateException("timer not scheduled"); 238 } 239 timer.cancel(); 240 timer = null; 241 } 242 243 @SuppressWarnings("deprecation") 244 public static void killThreads(Set<Long> ids) { 245 Map<Long, Thread> threads = getThreads(); 246 for (long id : ids) { 247 Thread thread = threads.get(id); 248 if (thread == null) { 249 continue; 250 } 251 thread.stop(); 252 } 253 } 254 255 protected static Map<Long, Thread> getThreads() { 256 ThreadGroup root = rootGroup(Thread.currentThread().getThreadGroup()); 257 int nThreads = root.activeCount(); 258 Thread[] threads = new Thread[2 * nThreads]; 259 root.enumerate(threads); 260 Map<Long, Thread> map = new HashMap<>(threads.length); 261 for (Thread thread : threads) { 262 if (thread == null) { 263 continue; 264 } 265 map.put(thread.getId(), thread); 266 } 267 return map; 268 } 269 270 protected static ThreadGroup rootGroup(ThreadGroup group) { 271 ThreadGroup parent = group.getParent(); 272 if (parent == null) { 273 return group; 274 } 275 return rootGroup(parent); 276 } 277 278}