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}