| /* |
| * 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; |
| import java.util.concurrent.ForkJoinPool; |
| |
| 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 { |
| // Eagerly initialize ForkJoinPool as we've seen the class initialization code interfere |
| // with the logic of the test when waiting for threads to be non-runnable. |
| Class.forName(ForkJoinPool.class.getName(), true, ForkJoinPool.class.getClassLoader()); |
| 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); |
| } |
| } |
| |