1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, Version |
3 | * 1.0, and under the Eclipse Public License, Version 1.0 |
4 | * (http://h2database.com/html/license.html). Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.util; |
7 | |
8 | import java.lang.management.ManagementFactory; |
9 | import java.lang.management.MonitorInfo; |
10 | import java.lang.management.ThreadInfo; |
11 | import java.lang.management.ThreadMXBean; |
12 | import java.util.ArrayList; |
13 | import java.util.Arrays; |
14 | import java.util.Comparator; |
15 | import java.util.HashSet; |
16 | import java.util.List; |
17 | import java.util.Map; |
18 | import java.util.Set; |
19 | import java.util.WeakHashMap; |
20 | |
21 | /** |
22 | * Utility to detect AB-BA deadlocks. |
23 | */ |
24 | public class AbbaLockingDetector implements Runnable { |
25 | |
26 | private int tickIntervalMs = 2; |
27 | private volatile boolean stop; |
28 | |
29 | private final ThreadMXBean threadMXBean = |
30 | ManagementFactory.getThreadMXBean(); |
31 | private Thread thread; |
32 | |
33 | /** |
34 | * Map of (object A) -> ( map of (object locked before object A) -> |
35 | * (stack trace where locked) ) |
36 | */ |
37 | private final Map<String, Map<String, String>> lockOrdering = |
38 | new WeakHashMap<String, Map<String, String>>(); |
39 | private final Set<String> knownDeadlocks = new HashSet<String>(); |
40 | |
41 | /** |
42 | * Start collecting locking data. |
43 | * |
44 | * @return this |
45 | */ |
46 | public AbbaLockingDetector startCollecting() { |
47 | thread = new Thread(this, "AbbaLockingDetector"); |
48 | thread.setDaemon(true); |
49 | thread.start(); |
50 | return this; |
51 | } |
52 | |
53 | /** |
54 | * Reset the state. |
55 | */ |
56 | public synchronized void reset() { |
57 | lockOrdering.clear(); |
58 | knownDeadlocks.clear(); |
59 | } |
60 | |
61 | /** |
62 | * Stop collecting. |
63 | * |
64 | * @return this |
65 | */ |
66 | public AbbaLockingDetector stopCollecting() { |
67 | stop = true; |
68 | if (thread != null) { |
69 | try { |
70 | thread.join(); |
71 | } catch (InterruptedException e) { |
72 | // ignore |
73 | } |
74 | thread = null; |
75 | } |
76 | return this; |
77 | } |
78 | |
79 | @Override |
80 | public void run() { |
81 | while (!stop) { |
82 | try { |
83 | tick(); |
84 | } catch (Throwable t) { |
85 | break; |
86 | } |
87 | } |
88 | } |
89 | |
90 | private void tick() { |
91 | if (tickIntervalMs > 0) { |
92 | try { |
93 | Thread.sleep(tickIntervalMs); |
94 | } catch (InterruptedException ex) { |
95 | // ignore |
96 | } |
97 | } |
98 | |
99 | ThreadInfo[] list = threadMXBean.dumpAllThreads( |
100 | // lockedMonitors |
101 | true, |
102 | // lockedSynchronizers |
103 | false); |
104 | processThreadList(list); |
105 | } |
106 | |
107 | private void processThreadList(ThreadInfo[] threadInfoList) { |
108 | final List<String> lockOrder = new ArrayList<String>(); |
109 | for (ThreadInfo threadInfo : threadInfoList) { |
110 | lockOrder.clear(); |
111 | generateOrdering(lockOrder, threadInfo); |
112 | if (lockOrder.size() > 1) { |
113 | markHigher(lockOrder, threadInfo); |
114 | } |
115 | } |
116 | } |
117 | |
118 | /** |
119 | * We cannot simply call getLockedMonitors because it is not guaranteed to |
120 | * return the locks in the correct order. |
121 | */ |
122 | private static void generateOrdering(final List<String> lockOrder, |
123 | ThreadInfo info) { |
124 | final MonitorInfo[] lockedMonitors = info.getLockedMonitors(); |
125 | Arrays.sort(lockedMonitors, new Comparator<MonitorInfo>() { |
126 | @Override |
127 | public int compare(MonitorInfo a, MonitorInfo b) { |
128 | return b.getLockedStackDepth() - a.getLockedStackDepth(); |
129 | } |
130 | }); |
131 | for (MonitorInfo mi : lockedMonitors) { |
132 | String lockName = getObjectName(mi); |
133 | if (lockName.equals("sun.misc.Launcher$AppClassLoader")) { |
134 | // ignore, it shows up everywhere |
135 | continue; |
136 | } |
137 | // Ignore locks which are locked multiple times in |
138 | // succession - Java locks are recursive. |
139 | if (!lockOrder.contains(lockName)) { |
140 | lockOrder.add(lockName); |
141 | } |
142 | } |
143 | } |
144 | |
145 | private synchronized void markHigher(List<String> lockOrder, |
146 | ThreadInfo threadInfo) { |
147 | String topLock = lockOrder.get(lockOrder.size() - 1); |
148 | Map<String, String> map = lockOrdering.get(topLock); |
149 | if (map == null) { |
150 | map = new WeakHashMap<String, String>(); |
151 | lockOrdering.put(topLock, map); |
152 | } |
153 | String oldException = null; |
154 | for (int i = 0; i < lockOrder.size() - 1; i++) { |
155 | String olderLock = lockOrder.get(i); |
156 | Map<String, String> oldMap = lockOrdering.get(olderLock); |
157 | boolean foundDeadLock = false; |
158 | if (oldMap != null) { |
159 | String e = oldMap.get(topLock); |
160 | if (e != null) { |
161 | foundDeadLock = true; |
162 | String deadlockType = topLock + " " + olderLock; |
163 | if (!knownDeadlocks.contains(deadlockType)) { |
164 | System.out.println(topLock + " synchronized after \n " + olderLock |
165 | + ", but in the past before\n" + "AFTER\n" + |
166 | getStackTraceForThread(threadInfo) |
167 | + "BEFORE\n" + e); |
168 | knownDeadlocks.add(deadlockType); |
169 | } |
170 | } |
171 | } |
172 | if (!foundDeadLock && !map.containsKey(olderLock)) { |
173 | if (oldException == null) { |
174 | oldException = getStackTraceForThread(threadInfo); |
175 | } |
176 | map.put(olderLock, oldException); |
177 | } |
178 | } |
179 | } |
180 | |
181 | /** |
182 | * Dump data in the same format as {@link ThreadInfo#toString()}, but with |
183 | * some modifications (no stack frame limit, and removal of uninteresting |
184 | * stack frames) |
185 | */ |
186 | private static String getStackTraceForThread(ThreadInfo info) { |
187 | StringBuilder sb = new StringBuilder("\"" + |
188 | info.getThreadName() + "\"" + " Id=" + |
189 | info.getThreadId() + " " + info.getThreadState()); |
190 | if (info.getLockName() != null) { |
191 | sb.append(" on " + info.getLockName()); |
192 | } |
193 | if (info.getLockOwnerName() != null) { |
194 | sb.append(" owned by \"" + info.getLockOwnerName() + |
195 | "\" Id=" + info.getLockOwnerId()); |
196 | } |
197 | if (info.isSuspended()) { |
198 | sb.append(" (suspended)"); |
199 | } |
200 | if (info.isInNative()) { |
201 | sb.append(" (in native)"); |
202 | } |
203 | sb.append('\n'); |
204 | final StackTraceElement[] stackTrace = info.getStackTrace(); |
205 | final MonitorInfo[] lockedMonitors = info.getLockedMonitors(); |
206 | boolean startDumping = false; |
207 | for (int i = 0; i < stackTrace.length; i++) { |
208 | StackTraceElement e = stackTrace[i]; |
209 | if (startDumping) { |
210 | dumpStackTraceElement(info, sb, i, e); |
211 | } |
212 | |
213 | for (MonitorInfo mi : lockedMonitors) { |
214 | if (mi.getLockedStackDepth() == i) { |
215 | // Only start dumping the stack from the first time we lock |
216 | // something. |
217 | // Removes a lot of unnecessary noise from the output. |
218 | if (!startDumping) { |
219 | dumpStackTraceElement(info, sb, i, e); |
220 | startDumping = true; |
221 | } |
222 | sb.append("\t- locked " + mi); |
223 | sb.append('\n'); |
224 | } |
225 | } |
226 | } |
227 | return sb.toString(); |
228 | } |
229 | |
230 | private static void dumpStackTraceElement(ThreadInfo info, |
231 | StringBuilder sb, int i, StackTraceElement e) { |
232 | sb.append('\t').append("at ").append(e.toString()); |
233 | sb.append('\n'); |
234 | if (i == 0 && info.getLockInfo() != null) { |
235 | Thread.State ts = info.getThreadState(); |
236 | switch (ts) { |
237 | case BLOCKED: |
238 | sb.append("\t- blocked on " + info.getLockInfo()); |
239 | sb.append('\n'); |
240 | break; |
241 | case WAITING: |
242 | sb.append("\t- waiting on " + info.getLockInfo()); |
243 | sb.append('\n'); |
244 | break; |
245 | case TIMED_WAITING: |
246 | sb.append("\t- waiting on " + info.getLockInfo()); |
247 | sb.append('\n'); |
248 | break; |
249 | default: |
250 | } |
251 | } |
252 | } |
253 | |
254 | private static String getObjectName(MonitorInfo info) { |
255 | return info.getClassName() + "@" + |
256 | Integer.toHexString(info.getIdentityHashCode()); |
257 | } |
258 | |
259 | } |