package org.h2.util;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
public class AbbaDetector {
private static final boolean TRACE = false;
private static final ThreadLocal<Deque<Object>> STACK =
new ThreadLocal<Deque<Object>>() {
@Override protected Deque<Object> initialValue() {
return new ArrayDeque<>();
}
};
private static final Map<Object, Map<Object, Exception>> LOCK_ORDERING =
new WeakHashMap<>();
private static final Set<String> KNOWN_DEADLOCKS = new HashSet<>();
public static Object begin(Object o) {
if (o == null) {
o = new SecurityManager() {
Class<?> clazz = getClassContext()[2];
}.clazz;
}
Deque<Object> stack = STACK.get();
if (!stack.isEmpty()) {
if (stack.contains(o)) {
return o;
}
while (!stack.isEmpty()) {
Object last = stack.peek();
if (Thread.holdsLock(last)) {
break;
}
stack.pop();
}
}
if (TRACE) {
String thread = "[thread " + Thread.currentThread().getId() + "]";
String indent = new String(new char[stack.size() * 2]).replace((char) 0, ' ');
System.out.println(thread + " " + indent +
"sync " + getObjectName(o));
}
if (!stack.isEmpty()) {
markHigher(o, stack);
}
stack.push(o);
return o;
}
private static Object getTest(Object o) {
return o;
}
private static String getObjectName(Object o) {
return o.getClass().getSimpleName() + "@" + System.identityHashCode(o);
}
private static synchronized void markHigher(Object o, Deque<Object> older) {
Object test = getTest(o);
Map<Object, Exception> map = LOCK_ORDERING.get(test);
if (map == null) {
map = new WeakHashMap<>();
LOCK_ORDERING.put(test, map);
}
Exception oldException = null;
for (Object old : older) {
Object oldTest = getTest(old);
if (oldTest == test) {
continue;
}
Map<Object, Exception> oldMap = LOCK_ORDERING.get(oldTest);
if (oldMap != null) {
Exception e = oldMap.get(test);
if (e != null) {
String deadlockType = test.getClass() + " " + oldTest.getClass();
if (!KNOWN_DEADLOCKS.contains(deadlockType)) {
String message = getObjectName(test) +
" synchronized after \n " + getObjectName(oldTest) +
", but in the past before";
RuntimeException ex = new RuntimeException(message);
ex.initCause(e);
ex.printStackTrace(System.out);
KNOWN_DEADLOCKS.add(deadlockType);
}
}
}
if (!map.containsKey(oldTest)) {
if (oldException == null) {
oldException = new Exception("Before");
}
map.put(oldTest, oldException);
}
}
}
}