1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package art; 18 19 import java.lang.reflect.Method; 20 import java.util.concurrent.atomic.*; 21 import java.util.function.Function; 22 import java.util.stream.Stream; 23 import java.util.Arrays; 24 import java.util.Objects; 25 26 public class Monitors { 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)27 public native static void setupMonitorEvents( 28 Class<?> method_klass, 29 Method monitor_contended_enter_event, 30 Method monitor_contended_entered_event, 31 Method monitor_wait_event, 32 Method monitor_waited_event, 33 Class<?> lock_klass, 34 Thread thr); stopMonitorEvents()35 public native static void stopMonitorEvents(); 36 37 public static class NamedLock { 38 public final String name; 39 private volatile int calledNotify; NamedLock(String name)40 public NamedLock(String name) { 41 this.name = name; 42 calledNotify = 0; 43 } 44 toString()45 public String toString() { 46 return String.format("NamedLock[%s]", name); 47 } 48 DoWait()49 public final void DoWait() throws Exception { 50 final int v = calledNotify; 51 while (v == calledNotify) { 52 wait(); 53 } 54 } 55 DoWait(long t)56 public final void DoWait(long t) throws Exception { 57 final int v = calledNotify; 58 final long target = System.currentTimeMillis() + (t / 2); 59 while (v == calledNotify && (t < 0 || System.currentTimeMillis() < target)) { 60 wait(t); 61 } 62 } 63 DoNotifyAll()64 public final void DoNotifyAll() throws Exception { 65 calledNotify++; 66 notifyAll(); 67 } 68 DoNotify()69 public final void DoNotify() throws Exception { 70 calledNotify++; 71 notify(); 72 } 73 } 74 75 public static final class MonitorUsage { 76 public final Object monitor; 77 public final Thread owner; 78 public final int entryCount; 79 public final Thread[] waiters; 80 public final Thread[] notifyWaiters; 81 MonitorUsage( Object monitor, Thread owner, int entryCount, Thread[] waiters, Thread[] notifyWaiters)82 public MonitorUsage( 83 Object monitor, 84 Thread owner, 85 int entryCount, 86 Thread[] waiters, 87 Thread[] notifyWaiters) { 88 this.monitor = monitor; 89 this.entryCount = entryCount; 90 this.owner = owner; 91 this.waiters = waiters; 92 this.notifyWaiters = notifyWaiters; 93 } 94 toNameList(Thread[] ts)95 private static String toNameList(Thread[] ts) { 96 return Arrays.toString(Arrays.stream(ts).map((Thread t) -> t.getName()).toArray()); 97 } 98 toString()99 public String toString() { 100 return String.format( 101 "MonitorUsage{ monitor: %s, owner: %s, entryCount: %d, waiters: %s, notify_waiters: %s }", 102 monitor, 103 (owner != null) ? owner.getName() : "<NULL>", 104 entryCount, 105 toNameList(waiters), 106 toNameList(notifyWaiters)); 107 } 108 } 109 getObjectMonitorUsage(Object monitor)110 public static native MonitorUsage getObjectMonitorUsage(Object monitor); getCurrentContendedMonitor(Thread thr)111 public static native Object getCurrentContendedMonitor(Thread thr); 112 113 public static class TestException extends Error { TestException()114 public TestException() { super(); } TestException(String s)115 public TestException(String s) { super(s); } TestException(String s, Throwable c)116 public TestException(String s, Throwable c) { super(s, c); } 117 } 118 119 public static class LockController { 120 private static enum Action { HOLD, RELEASE, NOTIFY, NOTIFY_ALL, WAIT, TIMED_WAIT } 121 122 public final NamedLock lock; 123 public final long timeout; 124 private final AtomicStampedReference<Action> action; 125 private volatile Thread runner = null; 126 private volatile boolean started = false; 127 private volatile boolean held = false; 128 private static final AtomicInteger cnt = new AtomicInteger(0); 129 private volatile Throwable exe; 130 LockController(NamedLock lock)131 public LockController(NamedLock lock) { 132 this(lock, 10 * 1000); 133 } LockController(NamedLock lock, long timeout)134 public LockController(NamedLock lock, long timeout) { 135 this.lock = lock; 136 this.timeout = timeout; 137 this.action = new AtomicStampedReference(Action.HOLD, 0); 138 this.exe = null; 139 } 140 IsWorkerThread(Thread thd)141 public boolean IsWorkerThread(Thread thd) { 142 return Objects.equals(runner, thd); 143 } 144 IsLocked()145 public boolean IsLocked() { 146 checkException(); 147 return held; 148 } 149 checkException()150 public void checkException() { 151 if (exe != null) { 152 throw new TestException("Exception thrown by other thread!", exe); 153 } 154 } 155 setAction(Action a)156 private void setAction(Action a) { 157 int stamp = action.getStamp(); 158 // Wait for it to be HOLD before updating. 159 while (!action.compareAndSet(Action.HOLD, a, stamp, stamp + 1)) { 160 stamp = action.getStamp(); 161 } 162 } 163 suspendWorker()164 public synchronized void suspendWorker() throws Exception { 165 checkException(); 166 if (runner == null) { 167 throw new TestException("We don't have any runner holding " + lock); 168 } 169 Suspension.suspend(runner); 170 } 171 getWorkerContendedMonitor()172 public Object getWorkerContendedMonitor() throws Exception { 173 checkException(); 174 if (runner == null) { 175 return null; 176 } 177 return getCurrentContendedMonitor(runner); 178 } 179 DoLock()180 public synchronized void DoLock() { 181 if (IsLocked()) { 182 throw new Error("lock is already acquired or being acquired."); 183 } 184 if (runner != null) { 185 throw new Error("Already have thread!"); 186 } 187 runner = new Thread(() -> { 188 started = true; 189 try { 190 synchronized (lock) { 191 held = true; 192 int[] stamp_h = new int[] { -1 }; 193 Action cur_action = Action.HOLD; 194 try { 195 while (true) { 196 cur_action = action.get(stamp_h); 197 int stamp = stamp_h[0]; 198 if (cur_action == Action.RELEASE) { 199 // The other thread will deal with reseting action. 200 break; 201 } 202 try { 203 switch (cur_action) { 204 case HOLD: 205 Thread.yield(); 206 break; 207 case NOTIFY: 208 lock.DoNotify(); 209 break; 210 case NOTIFY_ALL: 211 lock.DoNotifyAll(); 212 break; 213 case TIMED_WAIT: 214 lock.DoWait(timeout); 215 break; 216 case WAIT: 217 lock.DoWait(); 218 break; 219 default: 220 throw new Error("Unknown action " + action); 221 } 222 } finally { 223 // reset action back to hold if it isn't something else. 224 action.compareAndSet(cur_action, Action.HOLD, stamp, stamp+1); 225 } 226 } 227 } catch (Exception e) { 228 throw new TestException("Got an error while performing action " + cur_action, e); 229 } 230 } 231 } finally { 232 held = false; 233 started = false; 234 } 235 }, "Locker thread " + cnt.getAndIncrement() + " for " + lock); 236 // Make sure we can get any exceptions this throws. 237 runner.setUncaughtExceptionHandler((t, e) -> { exe = e; }); 238 runner.start(); 239 } 240 waitForLockToBeHeld()241 public void waitForLockToBeHeld() throws Exception { 242 while (true) { 243 if (IsLocked() && Objects.equals(runner, Monitors.getObjectMonitorUsage(lock).owner)) { 244 return; 245 } 246 } 247 } 248 waitForNotifySleep()249 public synchronized void waitForNotifySleep() throws Exception { 250 if (runner == null) { 251 throw new Error("No thread trying to lock!"); 252 } 253 do { 254 checkException(); 255 } while (!started || 256 !Arrays.asList(Monitors.getObjectMonitorUsage(lock).notifyWaiters).contains(runner)); 257 } 258 waitForContendedSleep()259 public synchronized void waitForContendedSleep() throws Exception { 260 if (runner == null) { 261 throw new Error("No thread trying to lock!"); 262 } 263 do { 264 checkException(); 265 } while (!started || 266 runner.getState() != Thread.State.BLOCKED || 267 !Arrays.asList(Monitors.getObjectMonitorUsage(lock).waiters).contains(runner)); 268 } 269 DoNotify()270 public synchronized void DoNotify() { 271 if (!IsLocked()) { 272 throw new Error("Not locked"); 273 } 274 setAction(Action.NOTIFY); 275 } 276 DoNotifyAll()277 public synchronized void DoNotifyAll() { 278 if (!IsLocked()) { 279 throw new Error("Not locked"); 280 } 281 setAction(Action.NOTIFY_ALL); 282 } 283 DoTimedWait()284 public synchronized void DoTimedWait() throws Exception { 285 if (!IsLocked()) { 286 throw new Error("Not locked"); 287 } 288 setAction(Action.TIMED_WAIT); 289 } 290 DoWait()291 public synchronized void DoWait() throws Exception { 292 if (!IsLocked()) { 293 throw new Error("Not locked"); 294 } 295 setAction(Action.WAIT); 296 } 297 interruptWorker()298 public synchronized void interruptWorker() throws Exception { 299 if (!IsLocked()) { 300 throw new Error("Not locked"); 301 } 302 runner.interrupt(); 303 } 304 waitForActionToFinish()305 public synchronized void waitForActionToFinish() throws Exception { 306 checkException(); 307 while (action.getReference() != Action.HOLD) { checkException(); } 308 } 309 DoUnlock()310 public synchronized void DoUnlock() throws Exception { 311 Error throwing = null; 312 if (!IsLocked()) { 313 // We might just be racing some exception that was thrown by the worker thread. Cache the 314 // exception, we will throw one from the worker before this one. 315 throwing = new Error("Not locked!"); 316 } 317 setAction(Action.RELEASE); 318 Thread run = runner; 319 runner = null; 320 while (held) {} 321 run.join(); 322 action.set(Action.HOLD, 0); 323 // Make sure to throw any exception that occurred since it might not have unlocked due to our 324 // request. 325 checkException(); 326 DoCleanup(); 327 if (throwing != null) { 328 throw throwing; 329 } 330 } 331 DoCleanup()332 public synchronized void DoCleanup() throws Exception { 333 if (runner != null) { 334 Thread run = runner; 335 runner = null; 336 while (held) {} 337 run.join(); 338 } 339 action.set(Action.HOLD, 0); 340 exe = null; 341 } 342 } 343 } 344 345