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