org.nuxeo.runtime.management.jvm.ThreadDeadlocksDetector.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.runtime.management.jvm.ThreadDeadlocksDetector.java

Source

/*
 * (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("tcheck-", ".tdump", 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);
    }

}