1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.util; |
7 | |
8 | import java.util.ArrayDeque; |
9 | import java.util.Deque; |
10 | import java.util.HashSet; |
11 | import java.util.Map; |
12 | import java.util.Set; |
13 | import java.util.WeakHashMap; |
14 | |
15 | /** |
16 | * Utility to detect AB-BA deadlocks. |
17 | */ |
18 | public class AbbaDetector { |
19 | |
20 | private static final boolean TRACE = false; |
21 | |
22 | private static final ThreadLocal<Deque<Object>> STACK = |
23 | new ThreadLocal<Deque<Object>>() { |
24 | @Override protected Deque<Object> initialValue() { |
25 | return new ArrayDeque<Object>(); |
26 | } |
27 | }; |
28 | |
29 | /** |
30 | * Map of (object A) -> ( |
31 | * map of (object locked before object A) -> |
32 | * (stack trace where locked) ) |
33 | */ |
34 | private static final Map<Object, Map<Object, Exception>> LOCK_ORDERING = |
35 | new WeakHashMap<Object, Map<Object, Exception>>(); |
36 | |
37 | private static final Set<String> KNOWN_DEADLOCKS = new HashSet<String>(); |
38 | |
39 | /** |
40 | * This method is called just before or just after an object is |
41 | * synchronized. |
42 | * |
43 | * @param o the object, or null for the current class |
44 | * @return the object that was passed |
45 | */ |
46 | public static Object begin(Object o) { |
47 | if (o == null) { |
48 | o = new SecurityManager() { |
49 | Class<?> clazz = getClassContext()[2]; |
50 | }.clazz; |
51 | } |
52 | Deque<Object> stack = STACK.get(); |
53 | if (!stack.isEmpty()) { |
54 | // Ignore locks which are locked multiple times in succession - |
55 | // Java locks are recursive |
56 | if (stack.contains(o)) { |
57 | // already synchronized on this |
58 | return o; |
59 | } |
60 | while (!stack.isEmpty()) { |
61 | Object last = stack.peek(); |
62 | if (Thread.holdsLock(last)) { |
63 | break; |
64 | } |
65 | stack.pop(); |
66 | } |
67 | } |
68 | if (TRACE) { |
69 | String thread = "[thread " + Thread.currentThread().getId() + "]"; |
70 | String indent = new String(new char[stack.size() * 2]).replace((char) 0, ' '); |
71 | System.out.println(thread + " " + indent + |
72 | "sync " + getObjectName(o)); |
73 | } |
74 | if (stack.size() > 0) { |
75 | markHigher(o, stack); |
76 | } |
77 | stack.push(o); |
78 | return o; |
79 | } |
80 | |
81 | private static Object getTest(Object o) { |
82 | // return o.getClass(); |
83 | return o; |
84 | } |
85 | |
86 | private static String getObjectName(Object o) { |
87 | return o.getClass().getSimpleName() + "@" + System.identityHashCode(o); |
88 | } |
89 | |
90 | private static synchronized void markHigher(Object o, Deque<Object> older) { |
91 | Object test = getTest(o); |
92 | Map<Object, Exception> map = LOCK_ORDERING.get(test); |
93 | if (map == null) { |
94 | map = new WeakHashMap<Object, Exception>(); |
95 | LOCK_ORDERING.put(test, map); |
96 | } |
97 | Exception oldException = null; |
98 | for (Object old : older) { |
99 | Object oldTest = getTest(old); |
100 | if (oldTest == test) { |
101 | continue; |
102 | } |
103 | Map<Object, Exception> oldMap = LOCK_ORDERING.get(oldTest); |
104 | if (oldMap != null) { |
105 | Exception e = oldMap.get(test); |
106 | if (e != null) { |
107 | String deadlockType = test.getClass() + " " + oldTest.getClass(); |
108 | if (!KNOWN_DEADLOCKS.contains(deadlockType)) { |
109 | String message = getObjectName(test) + |
110 | " synchronized after \n " + getObjectName(oldTest) + |
111 | ", but in the past before"; |
112 | RuntimeException ex = new RuntimeException(message); |
113 | ex.initCause(e); |
114 | ex.printStackTrace(System.out); |
115 | // throw ex; |
116 | KNOWN_DEADLOCKS.add(deadlockType); |
117 | } |
118 | } |
119 | } |
120 | if (!map.containsKey(oldTest)) { |
121 | if (oldException == null) { |
122 | oldException = new Exception("Before"); |
123 | } |
124 | map.put(oldTest, oldException); |
125 | } |
126 | } |
127 | } |
128 | |
129 | } |