Alex Light | ce56864 | 2017-09-05 16:54:25 -0700 | [diff] [blame] | 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 | |
Alex Light | 77fee87 | 2017-09-05 14:51:49 -0700 | [diff] [blame] | 19 | import java.lang.reflect.Method; |
| 20 | import java.util.concurrent.atomic.*; |
Alex Light | ce56864 | 2017-09-05 16:54:25 -0700 | [diff] [blame] | 21 | import java.util.function.Function; |
| 22 | import java.util.stream.Stream; |
Alex Light | 77fee87 | 2017-09-05 14:51:49 -0700 | [diff] [blame] | 23 | import java.util.Arrays; |
| 24 | import java.util.Objects; |
Alex Light | ce56864 | 2017-09-05 16:54:25 -0700 | [diff] [blame] | 25 | |
| 26 | public class Monitors { |
Alex Light | 77fee87 | 2017-09-05 14:51:49 -0700 | [diff] [blame] | 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); |
| 35 | public native static void stopMonitorEvents(); |
| 36 | |
Alex Light | ce56864 | 2017-09-05 16:54:25 -0700 | [diff] [blame] | 37 | public static class NamedLock { |
| 38 | public final String name; |
Alex Light | beae5ac | 2017-09-21 13:21:51 -0700 | [diff] [blame] | 39 | private volatile int calledNotify; |
Alex Light | ce56864 | 2017-09-05 16:54:25 -0700 | [diff] [blame] | 40 | public NamedLock(String name) { |
| 41 | this.name = name; |
Alex Light | beae5ac | 2017-09-21 13:21:51 -0700 | [diff] [blame] | 42 | calledNotify = 0; |
Alex Light | ce56864 | 2017-09-05 16:54:25 -0700 | [diff] [blame] | 43 | } |
Alex Light | beae5ac | 2017-09-21 13:21:51 -0700 | [diff] [blame] | 44 | |
Alex Light | ce56864 | 2017-09-05 16:54:25 -0700 | [diff] [blame] | 45 | public String toString() { |
| 46 | return String.format("NamedLock[%s]", name); |
| 47 | } |
Alex Light | beae5ac | 2017-09-21 13:21:51 -0700 | [diff] [blame] | 48 | |
| 49 | public final void DoWait() throws Exception { |
| 50 | final int v = calledNotify; |
| 51 | while (v == calledNotify) { |
| 52 | wait(); |
| 53 | } |
| 54 | } |
| 55 | |
| 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 | |
| 64 | public final void DoNotifyAll() throws Exception { |
| 65 | calledNotify++; |
| 66 | notifyAll(); |
| 67 | } |
| 68 | |
| 69 | public final void DoNotify() throws Exception { |
| 70 | calledNotify++; |
| 71 | notify(); |
| 72 | } |
Alex Light | ce56864 | 2017-09-05 16:54:25 -0700 | [diff] [blame] | 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 | |
| 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 | |
| 95 | private static String toNameList(Thread[] ts) { |
| 96 | return Arrays.toString(Arrays.stream(ts).map((Thread t) -> t.getName()).toArray()); |
| 97 | } |
| 98 | |
| 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 | |
| 110 | public static native MonitorUsage getObjectMonitorUsage(Object monitor); |
Alex Light | 41006c6 | 2017-09-14 09:51:14 -0700 | [diff] [blame] | 111 | public static native Object getCurrentContendedMonitor(Thread thr); |
Alex Light | 77fee87 | 2017-09-05 14:51:49 -0700 | [diff] [blame] | 112 | |
| 113 | public static class TestException extends Error { |
| 114 | public TestException() { super(); } |
| 115 | public TestException(String s) { super(s); } |
| 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 | |
Alex Light | beae5ac | 2017-09-21 13:21:51 -0700 | [diff] [blame] | 122 | public final NamedLock lock; |
Alex Light | 77fee87 | 2017-09-05 14:51:49 -0700 | [diff] [blame] | 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 | |
Alex Light | beae5ac | 2017-09-21 13:21:51 -0700 | [diff] [blame] | 131 | public LockController(NamedLock lock) { |
Alex Light | 77fee87 | 2017-09-05 14:51:49 -0700 | [diff] [blame] | 132 | this(lock, 10 * 1000); |
| 133 | } |
Alex Light | beae5ac | 2017-09-21 13:21:51 -0700 | [diff] [blame] | 134 | public LockController(NamedLock lock, long timeout) { |
Alex Light | 77fee87 | 2017-09-05 14:51:49 -0700 | [diff] [blame] | 135 | this.lock = lock; |
| 136 | this.timeout = timeout; |
| 137 | this.action = new AtomicStampedReference(Action.HOLD, 0); |
| 138 | this.exe = null; |
| 139 | } |
| 140 | |
| 141 | public boolean IsWorkerThread(Thread thd) { |
| 142 | return Objects.equals(runner, thd); |
| 143 | } |
| 144 | |
| 145 | public boolean IsLocked() { |
| 146 | checkException(); |
| 147 | return held; |
| 148 | } |
| 149 | |
| 150 | public void checkException() { |
| 151 | if (exe != null) { |
| 152 | throw new TestException("Exception thrown by other thread!", exe); |
| 153 | } |
| 154 | } |
| 155 | |
| 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 | |
Alex Light | 41006c6 | 2017-09-14 09:51:14 -0700 | [diff] [blame] | 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 | |
| 172 | public Object getWorkerContendedMonitor() throws Exception { |
| 173 | checkException(); |
| 174 | if (runner == null) { |
| 175 | return null; |
| 176 | } |
| 177 | return getCurrentContendedMonitor(runner); |
| 178 | } |
| 179 | |
Alex Light | 77fee87 | 2017-09-05 14:51:49 -0700 | [diff] [blame] | 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: |
Alex Light | beae5ac | 2017-09-21 13:21:51 -0700 | [diff] [blame] | 208 | lock.DoNotify(); |
Alex Light | 77fee87 | 2017-09-05 14:51:49 -0700 | [diff] [blame] | 209 | break; |
| 210 | case NOTIFY_ALL: |
Alex Light | beae5ac | 2017-09-21 13:21:51 -0700 | [diff] [blame] | 211 | lock.DoNotifyAll(); |
Alex Light | 77fee87 | 2017-09-05 14:51:49 -0700 | [diff] [blame] | 212 | break; |
| 213 | case TIMED_WAIT: |
Alex Light | beae5ac | 2017-09-21 13:21:51 -0700 | [diff] [blame] | 214 | lock.DoWait(timeout); |
Alex Light | 77fee87 | 2017-09-05 14:51:49 -0700 | [diff] [blame] | 215 | break; |
| 216 | case WAIT: |
Alex Light | beae5ac | 2017-09-21 13:21:51 -0700 | [diff] [blame] | 217 | lock.DoWait(); |
Alex Light | 77fee87 | 2017-09-05 14:51:49 -0700 | [diff] [blame] | 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 | |
| 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 | |
| 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 | |
| 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 | |
| 270 | public synchronized void DoNotify() { |
| 271 | if (!IsLocked()) { |
| 272 | throw new Error("Not locked"); |
| 273 | } |
| 274 | setAction(Action.NOTIFY); |
| 275 | } |
| 276 | |
| 277 | public synchronized void DoNotifyAll() { |
| 278 | if (!IsLocked()) { |
| 279 | throw new Error("Not locked"); |
| 280 | } |
| 281 | setAction(Action.NOTIFY_ALL); |
| 282 | } |
| 283 | |
| 284 | public synchronized void DoTimedWait() throws Exception { |
| 285 | if (!IsLocked()) { |
| 286 | throw new Error("Not locked"); |
| 287 | } |
| 288 | setAction(Action.TIMED_WAIT); |
| 289 | } |
| 290 | |
| 291 | public synchronized void DoWait() throws Exception { |
| 292 | if (!IsLocked()) { |
| 293 | throw new Error("Not locked"); |
| 294 | } |
| 295 | setAction(Action.WAIT); |
| 296 | } |
| 297 | |
| 298 | public synchronized void interruptWorker() throws Exception { |
| 299 | if (!IsLocked()) { |
| 300 | throw new Error("Not locked"); |
| 301 | } |
| 302 | runner.interrupt(); |
| 303 | } |
| 304 | |
| 305 | public synchronized void waitForActionToFinish() throws Exception { |
| 306 | checkException(); |
| 307 | while (action.getReference() != Action.HOLD) { checkException(); } |
| 308 | } |
| 309 | |
| 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 | |
| 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 | } |
Alex Light | ce56864 | 2017-09-05 16:54:25 -0700 | [diff] [blame] | 343 | } |
| 344 | |