345 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			345 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Java
		
	
	
	
| /*
 | |
|  * 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;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 |