226 lines
8.1 KiB
Java
226 lines
8.1 KiB
Java
/*
|
|
* Copyright (C) 2017 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import java.lang.Thread.State;
|
|
import java.lang.reflect.Method;
|
|
import java.util.Arrays;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.BrokenBarrierException;
|
|
import java.util.concurrent.CyclicBarrier;
|
|
|
|
public class Main {
|
|
|
|
static class Runner implements Runnable {
|
|
List<Object> locks;
|
|
List<CyclicBarrier> barriers;
|
|
|
|
public Runner(List<Object> locks, List<CyclicBarrier> barriers) {
|
|
this.locks = locks;
|
|
this.barriers = barriers;
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
step(locks, barriers);
|
|
}
|
|
|
|
private void step(List<Object> l, List<CyclicBarrier> b) {
|
|
if (l.isEmpty()) {
|
|
// Nothing to do, sleep indefinitely.
|
|
try {
|
|
Thread.sleep(100000000);
|
|
} catch (InterruptedException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
} else {
|
|
Object lockObject = l.remove(0);
|
|
CyclicBarrier barrierObject = b.remove(0);
|
|
|
|
if (lockObject == null) {
|
|
// No lock object: only take barrier, recurse.
|
|
try {
|
|
barrierObject.await();
|
|
} catch (InterruptedException | BrokenBarrierException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
step(l, b);
|
|
} else if (barrierObject != null) {
|
|
// Have barrier: sync, wait and recurse.
|
|
synchronized(lockObject) {
|
|
try {
|
|
barrierObject.await();
|
|
} catch (InterruptedException | BrokenBarrierException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
step(l, b);
|
|
}
|
|
} else {
|
|
// Sync, and get next step (which is assumed to have object and barrier).
|
|
synchronized (lockObject) {
|
|
Object lockObject2 = l.remove(0);
|
|
CyclicBarrier barrierObject2 = b.remove(0);
|
|
synchronized(lockObject2) {
|
|
try {
|
|
barrierObject2.await();
|
|
} catch (InterruptedException | BrokenBarrierException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
step(l, b);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
try {
|
|
testCluster1();
|
|
} catch (Exception e) {
|
|
Map<Thread,StackTraceElement[]> stacks = Thread.getAllStackTraces();
|
|
for (Map.Entry<Thread,StackTraceElement[]> entry : stacks.entrySet()) {
|
|
System.out.println(entry.getKey());
|
|
System.out.println(Arrays.toString(entry.getValue()));
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
private static void testCluster1() throws Exception {
|
|
// Test setup (at deadlock):
|
|
//
|
|
// Thread 1:
|
|
// #0 step: synchornized(o3) { synchronized(o2) }
|
|
// #1 step: synchronized(o1)
|
|
//
|
|
// Thread 2:
|
|
// #0 step: synchronized(o1)
|
|
// #1 step: synchronized(o4) { synchronized(o2) }
|
|
//
|
|
LinkedList<Object> l1 = new LinkedList<>();
|
|
LinkedList<CyclicBarrier> b1 = new LinkedList<>();
|
|
LinkedList<Object> l2 = new LinkedList<>();
|
|
LinkedList<CyclicBarrier> b2 = new LinkedList<>();
|
|
|
|
Object o1 = new Object();
|
|
Object o2 = new Object();
|
|
Object o3 = new Object();
|
|
Object o4 = new Object();
|
|
|
|
l1.add(o1);
|
|
l1.add(o3);
|
|
l1.add(o2);
|
|
l2.add(o4);
|
|
l2.add(o2);
|
|
l2.add(o1);
|
|
|
|
CyclicBarrier c1 = new CyclicBarrier(3);
|
|
CyclicBarrier c2 = new CyclicBarrier(2);
|
|
b1.add(c1);
|
|
b1.add(null);
|
|
b1.add(c2);
|
|
b2.add(null);
|
|
b2.add(c1);
|
|
b2.add(c2);
|
|
|
|
Thread t1 = new Thread(new Runner(l1, b1));
|
|
t1.setDaemon(true);
|
|
t1.start();
|
|
Thread t2 = new Thread(new Runner(l2, b2));
|
|
t2.setDaemon(true);
|
|
t2.start();
|
|
|
|
c1.await();
|
|
|
|
waitNotRunnable(t1);
|
|
waitNotRunnable(t2);
|
|
Thread.sleep(250); // Unfortunately this seems necessary. :-(
|
|
|
|
// Thread 1.
|
|
{
|
|
Object[] stack1 = getAnnotatedStack(t1);
|
|
assertBlockedOn(stack1[0], o2); // Blocked on o2.
|
|
assertLocks(stack1[0], o3); // Locked o3.
|
|
assertStackTraceElementStep(stack1[0]);
|
|
|
|
assertBlockedOn(stack1[1], null); // Frame can't be blocked.
|
|
assertLocks(stack1[1], o1); // Locked o1.
|
|
assertStackTraceElementStep(stack1[1]);
|
|
}
|
|
|
|
// Thread 2.
|
|
{
|
|
Object[] stack2 = getAnnotatedStack(t2);
|
|
assertBlockedOn(stack2[0], o1); // Blocked on o1.
|
|
assertLocks(stack2[0]); // Nothing locked.
|
|
assertStackTraceElementStep(stack2[0]);
|
|
|
|
assertBlockedOn(stack2[1], null); // Frame can't be blocked.
|
|
assertLocks(stack2[1], o4, o2); // Locked o4, o2.
|
|
assertStackTraceElementStep(stack2[1]);
|
|
}
|
|
}
|
|
|
|
private static void waitNotRunnable(Thread t) throws InterruptedException {
|
|
while (t.getState() == State.RUNNABLE) {
|
|
Thread.sleep(100);
|
|
}
|
|
}
|
|
|
|
private static Object[] getAnnotatedStack(Thread t) throws Exception {
|
|
Class<?> vmStack = Class.forName("dalvik.system.VMStack");
|
|
Method m = vmStack.getDeclaredMethod("getAnnotatedThreadStackTrace", Thread.class);
|
|
return (Object[]) m.invoke(null, t);
|
|
}
|
|
|
|
private static void assertEquals(Object o1, Object o2) {
|
|
if (o1 != o2) {
|
|
throw new RuntimeException("Expected " + o1 + " == " + o2);
|
|
}
|
|
}
|
|
private static void assertLocks(Object fromTrace, Object... locks) throws Exception {
|
|
Object fieldValue = fromTrace.getClass().getDeclaredMethod("getHeldLocks").
|
|
invoke(fromTrace);
|
|
assertEquals((Object[]) fieldValue,
|
|
(locks == null) ? null : (locks.length == 0 ? null : locks));
|
|
}
|
|
private static void assertBlockedOn(Object fromTrace, Object block) throws Exception {
|
|
Object fieldValue = fromTrace.getClass().getDeclaredMethod("getBlockedOn").
|
|
invoke(fromTrace);
|
|
assertEquals(fieldValue, block);
|
|
}
|
|
private static void assertEquals(Object[] o1, Object[] o2) {
|
|
if (!Arrays.equals(o1, o2)) {
|
|
throw new RuntimeException(
|
|
"Expected " + Arrays.toString(o1) + " == " + Arrays.toString(o2));
|
|
}
|
|
}
|
|
private static void assertStackTraceElementStep(Object o) throws Exception {
|
|
Object fieldValue = o.getClass().getDeclaredMethod("getStackTraceElement").invoke(o);
|
|
if (fieldValue instanceof StackTraceElement) {
|
|
StackTraceElement elem = (StackTraceElement) fieldValue;
|
|
if (!elem.getMethodName().equals("step")) {
|
|
throw new RuntimeException("Expected step method");
|
|
}
|
|
return;
|
|
}
|
|
throw new RuntimeException("Expected StackTraceElement " + fieldValue + " / " + o);
|
|
}
|
|
}
|
|
|