diff options
Diffstat (limited to 'test/jvmti-common/Monitors.java')
-rw-r--r-- | test/jvmti-common/Monitors.java | 344 |
1 files changed, 344 insertions, 0 deletions
diff --git a/test/jvmti-common/Monitors.java b/test/jvmti-common/Monitors.java new file mode 100644 index 0000000000..7fe2b60c1e --- /dev/null +++ b/test/jvmti-common/Monitors.java @@ -0,0 +1,344 @@ +/* + * 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; + } + } +} + |