You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
345 lines
10 KiB
345 lines
10 KiB
/*
|
|
* 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;
|
|
}
|
|
}
|
|
}
|
|
|