/* * (C) Copyright 2012 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * matic */ package org.nuxeo.runtime.management.jvm; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.lang.management.LockInfo; import java.lang.management.ManagementFactory; import java.lang.management.MonitorInfo; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class ThreadDeadlocksDetector { protected Timer timer; protected final ThreadMXBean mgmt = ManagementFactory.getThreadMXBean(); protected final Printer printer = new JVM16Printer(); protected static final Log log = LogFactory.getLog(ThreadDeadlocksDetector.class); public interface Printer { void print(final StringBuilder sb, final ThreadInfo thread); void printMonitors(final StringBuilder sb, final MonitorInfo[] monitors, final int index); void printLock(StringBuilder sb, LockInfo lock); } public static class JVM16Printer implements Printer { protected final ThreadMXBean mbean = ManagementFactory.getThreadMXBean(); @Override public void print(final StringBuilder sb, final ThreadInfo thread) { MonitorInfo[] monitors = null; if (mbean.isObjectMonitorUsageSupported()) { monitors = thread.getLockedMonitors(); } sb.append("\n\"" + thread.getThreadName() + // NOI18N "\" - Thread t@" + thread.getThreadId() + "\n"); // NOI18N sb.append(" java.lang.Thread.State: " + thread.getThreadState()); // NOI18N sb.append("\n"); // NOI18N int index = 0; for (StackTraceElement st : thread.getStackTrace()) { LockInfo lock = thread.getLockInfo(); String lockOwner = thread.getLockOwnerName(); sb.append("\tat " + st.toString() + "\n"); // NOI18N if (index == 0) { if ("java.lang.Object".equals(st.getClassName()) && // NOI18N "wait".equals(st.getMethodName())) { // NOI18N if (lock != null) { sb.append("\t- waiting on "); // NOI18N printLock(sb, lock); sb.append("\n"); // NOI18N } } else if (lock != null) { if (lockOwner == null) { sb.append("\t- parking to wait for "); // NOI18N printLock(sb, lock); sb.append("\n"); // NOI18N } else { sb.append("\t- waiting to lock "); // NOI18N printLock(sb, lock); sb.append(" owned by \"" + lockOwner + "\" t@" + thread.getLockOwnerId() + "\n"); // NOI18N } } } printMonitors(sb, monitors, index); index++; } StringBuilder jnisb = new StringBuilder(); printMonitors(jnisb, monitors, -1); if (jnisb.length() > 0) { sb.append(" JNI locked monitors:\n"); sb.append(jnisb); } if (mbean.isSynchronizerUsageSupported()) { sb.append("\n Locked ownable synchronizers:"); // NOI18N LockInfo[] synchronizers = thread.getLockedSynchronizers(); if (synchronizers == null || synchronizers.length == 0) { sb.append("\n\t- None\n"); // NOI18N } else { for (LockInfo li : synchronizers) { sb.append("\n\t- locked "); // NOI18N printLock(sb, li); sb.append("\n"); // NOI18N } } } } @Override public void printMonitors(final StringBuilder sb, final MonitorInfo[] monitors, final int index) { if (monitors != null) { for (MonitorInfo mi : monitors) { if (mi.getLockedStackDepth() == index) { sb.append("\t- locked "); // NOI18N printLock(sb, mi); sb.append("\n"); // NOI18N } } } } @Override public void printLock(StringBuilder sb, LockInfo lock) { String id = Integer.toHexString(lock.getIdentityHashCode()); String className = lock.getClassName(); sb.append("<" + id + "> (a " + className + ")"); // NOI18N } } public interface Listener { void deadlockDetected(long[] ids, File dumpFile); } public static class KillListener implements Listener { @Override public void deadlockDetected(long[] ids, File dumpFile) { log.error("Exiting, detected threads dead locks, see thread dump in " + dumpFile.getPath()); System.exit(1); } } public File dump(long[] lockedIds) throws IOException { File file = File.createTempFile("tdump-", ".log", new File("target")); FileOutputStream os = new FileOutputStream(file); ThreadInfo[] infos = mgmt.dumpAllThreads(true, true); try { for (ThreadInfo info : infos) { StringBuilder sb = new StringBuilder(); printer.print(sb, info); os.write(sb.toString().getBytes("UTF-8")); } StringBuilder sb = new StringBuilder(); sb.append("Locked threads: "); String comma = ""; for (long lockedId : lockedIds) { sb.append(comma).append(lockedId); comma = ","; } os.write(sb.toString().getBytes("UTF-8")); } finally { os.close(); } return file; } public long[] detectThreadLock() { long[] findMonitorDeadlockedThreads = mgmt.findMonitorDeadlockedThreads(); if (findMonitorDeadlockedThreads == null) { return new long[0]; } return findMonitorDeadlockedThreads; } protected class Task extends TimerTask { protected final Listener listener; protected Task(Listener listener) { this.listener = listener; } @Override public void run() { long[] ids = detectThreadLock(); if (ids.length == 0) { return; } File dumpFile; try { dumpFile = dump(ids); } catch (IOException e) { log.error("Cannot dump threads", e); dumpFile = new File("/dev/null"); } listener.deadlockDetected(ids, dumpFile); } } public void schedule(long period, Listener listener) { if (timer != null) { throw new IllegalStateException("timer already scheduled"); } timer = new Timer("Thread Deadlocks Detector"); timer.schedule(new Task(listener), 1000, period); } public void cancel() { if (timer == null) { throw new IllegalStateException("timer not scheduled"); } timer.cancel(); timer = null; } @SuppressWarnings("deprecation") public static void killThreads(Set<Long> ids) { Map<Long, Thread> threads = getThreads(); for (long id : ids) { Thread thread = threads.get(id); if (thread == null) { continue; } thread.stop(); } } protected static Map<Long, Thread> getThreads() { ThreadGroup root = rootGroup(Thread.currentThread().getThreadGroup()); int nThreads = root.activeCount(); Thread[] threads = new Thread[2 * nThreads]; root.enumerate(threads); Map<Long, Thread> map = new HashMap<Long, Thread>(threads.length); for (Thread thread : threads) { if (thread == null) { continue; } map.put(thread.getId(), thread); } return map; } protected static ThreadGroup rootGroup(ThreadGroup group) { ThreadGroup parent = group.getParent(); if (parent == null) { return group; } return rootGroup(parent); } }