/* * 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. */ package art; import java.lang.reflect.Method; import java.util.concurrent.atomic.*; import java.util.function.Function; import java.util.stream.Stream; import java.util.Arrays; import java.util.Objects; public class Monitors { public native static void setupMonitorEvents( Class method_klass, Method monitor_contended_enter_event, Method monitor_contended_entered_event, Method monitor_wait_event, Method monitor_waited_event, Class lock_klass, Thread thr); public native static void stopMonitorEvents(); public static class NamedLock { public final String name; private volatile int calledNotify; public NamedLock(String name) { this.name = name; calledNotify = 0; } public String toString() { return String.format("NamedLock[%s]", name); } public final void DoWait() throws Exception { final int v = calledNotify; while (v == calledNotify) { wait(); } } public final void DoWait(long t) throws Exception { final int v = calledNotify; final long target = System.currentTimeMillis() + (t / 2); while (v == calledNotify && (t < 0 || System.currentTimeMillis() < target)) { wait(t); } } public final void DoNotifyAll() throws Exception { calledNotify++; notifyAll(); } public final void DoNotify() throws Exception { calledNotify++; notify(); } } public static final class MonitorUsage { public final Object monitor; public final Thread owner; public final int entryCount; public final Thread[] waiters; public final Thread[] notifyWaiters; public MonitorUsage( Object monitor, Thread owner, int entryCount, Thread[] waiters, Thread[] notifyWaiters) { this.monitor = monitor; this.entryCount = entryCount; this.owner = owner; this.waiters = waiters; this.notifyWaiters = notifyWaiters; } private static String toNameList(Thread[] ts) { return Arrays.toString(Arrays.stream(ts).map((Thread t) -> t.getName()).toArray()); } public String toString() { return String.format( "MonitorUsage{ monitor: %s, owner: %s, entryCount: %d, waiters: %s, notify_waiters: %s }", monitor, (owner != null) ? owner.getName() : "", entryCount, toNameList(waiters), toNameList(notifyWaiters)); } } public static native MonitorUsage getObjectMonitorUsage(Object monitor); public static native Object getCurrentContendedMonitor(Thread thr); public static class TestException extends Error { public TestException() { super(); } public TestException(String s) { super(s); } public TestException(String s, Throwable c) { super(s, c); } } public static class LockController { private static enum Action { HOLD, RELEASE, NOTIFY, NOTIFY_ALL, WAIT, TIMED_WAIT } public final NamedLock lock; public final long timeout; private final AtomicStampedReference action; private volatile Thread runner = null; private volatile boolean started = false; private volatile boolean held = false; private static final AtomicInteger cnt = new AtomicInteger(0); private volatile Throwable exe; public LockController(NamedLock lock) { this(lock, 10 * 1000); } public LockController(NamedLock lock, long timeout) { this.lock = lock; this.timeout = timeout; this.action = new AtomicStampedReference(Action.HOLD, 0); this.exe = null; } public boolean IsWorkerThread(Thread thd) { return Objects.equals(runner, thd); } public boolean IsLocked() { checkException(); return held; } public void checkException() { if (exe != null) { throw new TestException("Exception thrown by other thread!", exe); } } private void setAction(Action a) { int stamp = action.getStamp(); // Wait for it to be HOLD before updating. while (!action.compareAndSet(Action.HOLD, a, stamp, stamp + 1)) { stamp = action.getStamp(); } } public synchronized void suspendWorker() throws Exception { checkException(); if (runner == null) { throw new TestException("We don't have any runner holding " + lock); } Suspension.suspend(runner); } public Object getWorkerContendedMonitor() throws Exception { checkException(); if (runner == null) { return null; } return getCurrentContendedMonitor(runner); } public synchronized void DoLock() { if (IsLocked()) { throw new Error("lock is already acquired or being acquired."); } if (runner != null) { throw new Error("Already have thread!"); } runner = new Thread(() -> { started = true; try { synchronized (lock) { held = true; int[] stamp_h = new int[] { -1 }; Action cur_action = Action.HOLD; try { while (true) { cur_action = action.get(stamp_h); int stamp = stamp_h[0]; if (cur_action == Action.RELEASE) { // The other thread will deal with reseting action. break; } try { switch (cur_action) { case HOLD: Thread.yield(); break; case NOTIFY: lock.DoNotify(); break; case NOTIFY_ALL: lock.DoNotifyAll(); break; case TIMED_WAIT: lock.DoWait(timeout); break; case WAIT: lock.DoWait(); break; default: throw new Error("Unknown action " + action); } } finally { // reset action back to hold if it isn't something else. action.compareAndSet(cur_action, Action.HOLD, stamp, stamp+1); } } } catch (Exception e) { throw new TestException("Got an error while performing action " + cur_action, e); } } } finally { held = false; started = false; } }, "Locker thread " + cnt.getAndIncrement() + " for " + lock); // Make sure we can get any exceptions this throws. runner.setUncaughtExceptionHandler((t, e) -> { exe = e; }); runner.start(); } public void waitForLockToBeHeld() throws Exception { while (true) { if (IsLocked() && Objects.equals(runner, Monitors.getObjectMonitorUsage(lock).owner)) { return; } } } public synchronized void waitForNotifySleep() throws Exception { if (runner == null) { throw new Error("No thread trying to lock!"); } do { checkException(); } while (!started || !Arrays.asList(Monitors.getObjectMonitorUsage(lock).notifyWaiters).contains(runner)); } public synchronized void waitForContendedSleep() throws Exception { if (runner == null) { throw new Error("No thread trying to lock!"); } do { checkException(); } while (!started || runner.getState() != Thread.State.BLOCKED || !Arrays.asList(Monitors.getObjectMonitorUsage(lock).waiters).contains(runner)); } public synchronized void DoNotify() { if (!IsLocked()) { throw new Error("Not locked"); } setAction(Action.NOTIFY); } public synchronized void DoNotifyAll() { if (!IsLocked()) { throw new Error("Not locked"); } setAction(Action.NOTIFY_ALL); } public synchronized void DoTimedWait() throws Exception { if (!IsLocked()) { throw new Error("Not locked"); } setAction(Action.TIMED_WAIT); } public synchronized void DoWait() throws Exception { if (!IsLocked()) { throw new Error("Not locked"); } setAction(Action.WAIT); } public synchronized void interruptWorker() throws Exception { if (!IsLocked()) { throw new Error("Not locked"); } runner.interrupt(); } public synchronized void waitForActionToFinish() throws Exception { checkException(); while (action.getReference() != Action.HOLD) { checkException(); } } public synchronized void DoUnlock() throws Exception { Error throwing = null; if (!IsLocked()) { // We might just be racing some exception that was thrown by the worker thread. Cache the // exception, we will throw one from the worker before this one. throwing = new Error("Not locked!"); } setAction(Action.RELEASE); Thread run = runner; runner = null; while (held) {} run.join(); action.set(Action.HOLD, 0); // Make sure to throw any exception that occurred since it might not have unlocked due to our // request. checkException(); DoCleanup(); if (throwing != null) { throw throwing; } } public synchronized void DoCleanup() throws Exception { if (runner != null) { Thread run = runner; runner = null; while (held) {} run.join(); } action.set(Action.HOLD, 0); exe = null; } } }