blob: 7fe2b60c1e4e11f76e441ae1d6545875a65d9619 [file] [log] [blame]
/*
* 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() : "<NULL>",
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> 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;
}
}
}