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}