blob: ef00728d10de850aad4c53a10e1d58dcf293de77 [file] [log] [blame]
Andreas Gampe319dbe82017-01-09 16:42:21 -08001/*
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
17import java.util.concurrent.CountDownLatch;
18import java.util.ArrayList;
19import java.util.Iterator;
20import java.util.List;
21
22public class Main {
23 public static void main(String[] args) throws Exception {
Andreas Gampe319dbe82017-01-09 16:42:21 -080024 doTest();
25 }
26
27 private static void doTest() throws Exception {
28 // Start a watchdog, to make sure on deadlocks etc the test dies.
29 startWatchdog();
30
31 sharedId = createRawMonitor();
32
33 output = new ArrayList<String>(100);
34
35 simpleTests(sharedId);
36
37 for (String s : output) {
38 System.out.println(s);
39 }
40 output.clear();
41
42 threadTests(sharedId);
43
44 destroyRawMonitor(sharedId);
45 }
46
47 private static void simpleTests(long id) {
48 unlock(id); // Should fail.
49
50 lock(id);
51 unlock(id);
52 unlock(id); // Should fail.
53
54 lock(id);
55 lock(id);
56 unlock(id);
57 unlock(id);
58 unlock(id); // Should fail.
59
60 rawWait(id, 0); // Should fail.
61 rawWait(id, -1); // Should fail.
62 rawWait(id, 1); // Should fail.
63
64 lock(id);
65 rawWait(id, 50);
66 unlock(id);
67 unlock(id); // Should fail.
68
69 rawNotify(id); // Should fail.
70 lock(id);
71 rawNotify(id);
72 unlock(id);
73 unlock(id); // Should fail.
74
75 rawNotifyAll(id); // Should fail.
76 lock(id);
77 rawNotifyAll(id);
78 unlock(id);
79 unlock(id); // Should fail.
80 }
81
82 private static void threadTests(final long id) throws Exception {
83 final int N = 10;
84
85 final CountDownLatch waitLatch = new CountDownLatch(N);
86 final CountDownLatch wait2Latch = new CountDownLatch(1);
87
88 Runnable r = new Runnable() {
89 @Override
90 public void run() {
91 lock(id);
92 waitLatch.countDown();
93 rawWait(id, 0);
94 firstAwakened = Thread.currentThread();
95 appendToLog("Awakened");
96 unlock(id);
97 wait2Latch.countDown();
98 }
99 };
100
101 List<Thread> threads = new ArrayList<Thread>();
102 for (int i = 0; i < N; i++) {
103 Thread t = new Thread(r);
104 threads.add(t);
105 t.start();
106 }
107
108 // Wait till all threads have been started.
109 waitLatch.await();
110
111 // Hopefully enough time for all the threads to progress into wait.
112 Thread.yield();
113 Thread.sleep(500);
114
115 // Wake up one.
116 lock(id);
117 rawNotify(id);
118 unlock(id);
119
120 wait2Latch.await();
121
122 // Wait a little bit more to see stragglers. This is flaky - spurious wakeups could
123 // make the test fail.
124 Thread.yield();
125 Thread.sleep(500);
126 if (firstAwakened != null) {
127 firstAwakened.join();
128 }
129
130 // Wake up everyone else.
131 lock(id);
132 rawNotifyAll(id);
133 unlock(id);
134
135 // Wait for everyone to die.
136 for (Thread t : threads) {
137 t.join();
138 }
139
140 // Check threaded output.
141 Iterator<String> it = output.iterator();
142 // 1) Start with N locks and Waits.
143 {
144 int locks = 0;
145 int waits = 0;
146 for (int i = 0; i < 2*N; i++) {
147 String s = it.next();
148 if (s.equals("Lock")) {
149 locks++;
150 } else if (s.equals("Wait")) {
151 if (locks <= waits) {
152 System.out.println(output);
153 throw new RuntimeException("Wait before Lock");
154 }
155 waits++;
156 } else {
157 System.out.println(output);
158 throw new RuntimeException("Unexpected operation: " + s);
159 }
160 }
161 }
162
163 // 2) Expect Lock + Notify + Unlock.
164 expect("Lock", it, output);
165 expect("Notify", it, output);
166 expect("Unlock", it, output);
167
168 // 3) A single thread wakes up, runs, and dies.
169 expect("Awakened", it, output);
170 expect("Unlock", it, output);
171
172 // 4) Expect Lock + NotifyAll + Unlock.
173 expect("Lock", it, output);
174 expect("NotifyAll", it, output);
175 expect("Unlock", it, output);
176
177 // 5) N-1 threads wake up, run, and die.
178 {
179 int expectedUnlocks = 0;
180 int ops = 2 * (N-1);
181 for (int i = 0; i < ops; i++) {
182 String s = it.next();
183 if (s.equals("Awakened")) {
184 expectedUnlocks++;
185 } else if (s.equals("Unlock")) {
186 expectedUnlocks--;
187 if (expectedUnlocks < 0) {
188 System.out.println(output);
189 throw new RuntimeException("Unexpected unlock");
190 }
191 }
192 }
193 }
194
195 // 6) That should be it.
196 if (it.hasNext()) {
197 System.out.println(output);
198 throw new RuntimeException("Unexpected trailing output, starting with " + it.next());
199 }
200
201 output.clear();
202 System.out.println("Done");
203 }
204
205 private static void expect(String s, Iterator<String> it, List<String> output) {
206 String t = it.next();
207 if (!s.equals(t)) {
208 System.out.println(output);
209 throw new RuntimeException("Expected " + s + " but got " + t);
210 }
211 }
212
213 private static void lock(long id) {
214 appendToLog("Lock");
215 rawMonitorEnter(id);
216 }
217
218 private static void unlock(long id) {
219 appendToLog("Unlock");
220 try {
221 rawMonitorExit(id);
222 } catch (RuntimeException e) {
223 appendToLog(e.getMessage());
224 }
225 }
226
227 private static void rawWait(long id, long millis) {
228 appendToLog("Wait");
229 try {
230 rawMonitorWait(id, millis);
231 } catch (RuntimeException e) {
232 appendToLog(e.getMessage());
233 }
234 }
235
236 private static void rawNotify(long id) {
237 appendToLog("Notify");
238 try {
239 rawMonitorNotify(id);
240 } catch (RuntimeException e) {
241 appendToLog(e.getMessage());
242 }
243 }
244
245 private static void rawNotifyAll(long id) {
246 appendToLog("NotifyAll");
247 try {
248 rawMonitorNotifyAll(id);
249 } catch (RuntimeException e) {
250 appendToLog(e.getMessage());
251 }
252 }
253
254 private static synchronized void appendToLog(String s) {
255 output.add(s);
256 }
257
258 private static void startWatchdog() {
259 Runnable r = new Runnable() {
260 @Override
261 public void run() {
262 long start = System.currentTimeMillis();
263 // Give it a minute.
264 long end = 60 * 1000 + start;
265 for (;;) {
266 long delta = end - System.currentTimeMillis();
267 if (delta <= 0) {
268 break;
269 }
270
271 try {
272 Thread.currentThread().sleep(delta);
273 } catch (Exception e) {
274 }
275 }
276 System.out.println("TIMEOUT!");
277 System.exit(1);
278 }
279 };
280 Thread t = new Thread(r);
281 t.setDaemon(true);
282 t.start();
283 }
284
285 static volatile long sharedId;
286 static List<String> output;
287 static Thread firstAwakened;
288
289 private static native long createRawMonitor();
290 private static native void destroyRawMonitor(long id);
291 private static native void rawMonitorEnter(long id);
292 private static native void rawMonitorExit(long id);
293 private static native void rawMonitorWait(long id, long millis);
294 private static native void rawMonitorNotify(long id);
295 private static native void rawMonitorNotifyAll(long id);
296}