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