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}