blob: 6ad160c1a6fd8cfde840cedeea49514353c652b2 [file] [log] [blame]
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -07001/*
2 * Copyright (C) 2011 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
Andreas Gampe1c83cbc2014-07-22 18:52:29 -070017import java.lang.reflect.*;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -070018import java.util.ArrayList;
19import java.util.Arrays;
20import java.util.Collections;
21import java.util.HashMap;
Andreas Gampef2fdc732014-06-11 08:20:47 -070022import java.util.HashSet;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -070023import java.util.List;
24import java.util.Map;
Andreas Gampef2fdc732014-06-11 08:20:47 -070025import java.util.Set;
Roland Levillain0c806b12017-08-15 15:09:55 +010026import java.util.concurrent.Semaphore;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -070027
28// Run on host with:
29// javac ThreadTest.java && java ThreadStress && rm *.class
Andreas Gampef2fdc732014-06-11 08:20:47 -070030// Through run-test:
31// test/run-test {run-test-args} 004-ThreadStress [Main {ThreadStress-args}]
32// (It is important to pass Main if you want to give parameters...)
33//
34// ThreadStress command line parameters:
35// -n X ............ number of threads
Man Caodef3fcd2015-08-10 15:51:27 -070036// -d X ............ number of daemon threads
Andreas Gampef2fdc732014-06-11 08:20:47 -070037// -o X ............ number of overall operations
38// -t X ............ number of operations per thread
Roland Levillain0c806b12017-08-15 15:09:55 +010039// -p X ............ number of permits granted by semaphore
Andreas Gampef2fdc732014-06-11 08:20:47 -070040// --dumpmap ....... print the frequency map
41// -oom:X .......... frequency of OOM (double)
Roland Levillain0c806b12017-08-15 15:09:55 +010042// -sigquit:X ...... frequency of SigQuit (double)
43// -alloc:X ........ frequency of Alloc (double)
44// -largealloc:X ... frequency of LargeAlloc (double)
45// -stacktrace:X ... frequency of StackTrace (double)
46// -exit:X ......... frequency of Exit (double)
47// -sleep:X ........ frequency of Sleep (double)
48// -wait:X ......... frequency of Wait (double)
49// -timedwait:X .... frequency of TimedWait (double)
50// -syncandwork:X .. frequency of SyncAndWork (double)
51// -queuedwait:X ... frequency of QueuedWait (double)
Andreas Gampef2fdc732014-06-11 08:20:47 -070052
Andreas Gampe1c83cbc2014-07-22 18:52:29 -070053public class Main implements Runnable {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -070054
Brian Carlstrom4514d3c2011-10-21 17:01:31 -070055 public static final boolean DEBUG = false;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -070056
Andreas Gampef2fdc732014-06-11 08:20:47 -070057 private static abstract class Operation {
58 /**
59 * Perform the action represented by this operation. Returns true if the thread should
Roland Levillain0c806b12017-08-15 15:09:55 +010060 * continue when executed by a runner (non-daemon) thread.
Andreas Gampef2fdc732014-06-11 08:20:47 -070061 */
62 public abstract boolean perform();
63 }
Elliott Hughes4cd121e2013-01-07 17:35:41 -080064
Andreas Gampef2fdc732014-06-11 08:20:47 -070065 private final static class OOM extends Operation {
Andreas Gampe059e6272016-01-05 12:57:56 -080066 private final static int ALLOC_SIZE = 1024;
67
Andreas Gampef2fdc732014-06-11 08:20:47 -070068 @Override
69 public boolean perform() {
70 try {
71 List<byte[]> l = new ArrayList<byte[]>();
72 while (true) {
Andreas Gampe059e6272016-01-05 12:57:56 -080073 l.add(new byte[ALLOC_SIZE]);
Andreas Gampef2fdc732014-06-11 08:20:47 -070074 }
75 } catch (OutOfMemoryError e) {
76 }
77 return true;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -070078 }
79 }
80
Andreas Gampef2fdc732014-06-11 08:20:47 -070081 private final static class SigQuit extends Operation {
82 private final static int sigquit;
83 private final static Method kill;
84 private final static int pid;
85
86 static {
87 int pidTemp = -1;
88 int sigquitTemp = -1;
89 Method killTemp = null;
90
91 try {
92 Class<?> osClass = Class.forName("android.system.Os");
93 Method getpid = osClass.getDeclaredMethod("getpid");
94 pidTemp = (Integer)getpid.invoke(null);
95
96 Class<?> osConstants = Class.forName("android.system.OsConstants");
97 Field sigquitField = osConstants.getDeclaredField("SIGQUIT");
98 sigquitTemp = (Integer)sigquitField.get(null);
99
100 killTemp = osClass.getDeclaredMethod("kill", int.class, int.class);
101 } catch (Exception e) {
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100102 Main.printThrowable(e);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700103 }
104
105 pid = pidTemp;
106 sigquit = sigquitTemp;
107 kill = killTemp;
108 }
109
110 @Override
111 public boolean perform() {
112 try {
113 kill.invoke(null, pid, sigquit);
Vladimir Marko5fe10262016-06-21 10:38:23 +0100114 } catch (OutOfMemoryError e) {
Andreas Gampef2fdc732014-06-11 08:20:47 -0700115 } catch (Exception e) {
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100116 if (!e.getClass().getName().equals(Main.errnoExceptionName)) {
117 Main.printThrowable(e);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700118 }
119 }
120 return true;
121 }
122 }
123
124 private final static class Alloc extends Operation {
Andreas Gampe059e6272016-01-05 12:57:56 -0800125 private final static int ALLOC_SIZE = 1024; // Needs to be small enough to not be in LOS.
126 private final static int ALLOC_COUNT = 1024;
127
Andreas Gampef2fdc732014-06-11 08:20:47 -0700128 @Override
129 public boolean perform() {
130 try {
131 List<byte[]> l = new ArrayList<byte[]>();
Andreas Gampe059e6272016-01-05 12:57:56 -0800132 for (int i = 0; i < ALLOC_COUNT; i++) {
133 l.add(new byte[ALLOC_SIZE]);
134 }
135 } catch (OutOfMemoryError e) {
136 }
137 return true;
138 }
139 }
140
141 private final static class LargeAlloc extends Operation {
142 private final static int PAGE_SIZE = 4096;
143 private final static int PAGE_SIZE_MODIFIER = 10; // Needs to be large enough for LOS.
144 private final static int ALLOC_COUNT = 100;
145
146 @Override
147 public boolean perform() {
148 try {
149 List<byte[]> l = new ArrayList<byte[]>();
150 for (int i = 0; i < ALLOC_COUNT; i++) {
151 l.add(new byte[PAGE_SIZE_MODIFIER * PAGE_SIZE]);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700152 }
153 } catch (OutOfMemoryError e) {
154 }
155 return true;
156 }
157 }
158
159 private final static class StackTrace extends Operation {
160 @Override
161 public boolean perform() {
Vladimir Marko5fe10262016-06-21 10:38:23 +0100162 try {
163 Thread.currentThread().getStackTrace();
164 } catch (OutOfMemoryError e) {
165 }
Andreas Gampef2fdc732014-06-11 08:20:47 -0700166 return true;
167 }
168 }
169
170 private final static class Exit extends Operation {
171 @Override
172 public boolean perform() {
173 return false;
174 }
175 }
176
177 private final static class Sleep extends Operation {
Andreas Gampe059e6272016-01-05 12:57:56 -0800178 private final static int SLEEP_TIME = 100;
179
Andreas Gampef2fdc732014-06-11 08:20:47 -0700180 @Override
181 public boolean perform() {
182 try {
Andreas Gampe059e6272016-01-05 12:57:56 -0800183 Thread.sleep(SLEEP_TIME);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700184 } catch (InterruptedException ignored) {
185 }
186 return true;
187 }
188 }
189
190 private final static class TimedWait extends Operation {
Andreas Gampe059e6272016-01-05 12:57:56 -0800191 private final static int SLEEP_TIME = 100;
192
Andreas Gampef2fdc732014-06-11 08:20:47 -0700193 private final Object lock;
194
195 public TimedWait(Object lock) {
196 this.lock = lock;
197 }
198
199 @Override
200 public boolean perform() {
201 synchronized (lock) {
202 try {
Andreas Gampe059e6272016-01-05 12:57:56 -0800203 lock.wait(SLEEP_TIME, 0);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700204 } catch (InterruptedException ignored) {
205 }
206 }
207 return true;
208 }
209 }
210
211 private final static class Wait extends Operation {
212 private final Object lock;
213
214 public Wait(Object lock) {
215 this.lock = lock;
216 }
217
218 @Override
219 public boolean perform() {
220 synchronized (lock) {
221 try {
222 lock.wait();
223 } catch (InterruptedException ignored) {
224 }
225 }
226 return true;
227 }
228 }
229
230 private final static class SyncAndWork extends Operation {
231 private final Object lock;
232
233 public SyncAndWork(Object lock) {
234 this.lock = lock;
235 }
236
237 @Override
238 public boolean perform() {
239 synchronized (lock) {
240 try {
Andreas Gamped0210e52017-06-23 13:38:09 -0700241 Thread.sleep((int)(Math.random() * 50 + 50));
Andreas Gampef2fdc732014-06-11 08:20:47 -0700242 } catch (InterruptedException ignored) {
243 }
244 }
245 return true;
246 }
247 }
248
Roland Levillain0c806b12017-08-15 15:09:55 +0100249 // An operation requiring the acquisition of a permit from a semaphore
250 // for its execution. This operation has been added to exercise
251 // java.util.concurrent.locks.AbstractQueuedSynchronizer, used in the
252 // implementation of java.util.concurrent.Semaphore. We use the latter,
253 // as the former is not supposed to be used directly (see b/63822989).
254 private final static class QueuedWait extends Operation {
255 private final static int SLEEP_TIME = 100;
256
257 private final Semaphore semaphore;
258
259 public QueuedWait(Semaphore semaphore) {
260 this.semaphore = semaphore;
261 }
262
263 @Override
264 public boolean perform() {
265 boolean permitAcquired = false;
266 try {
267 semaphore.acquire();
268 permitAcquired = true;
269 Thread.sleep(SLEEP_TIME);
Roland Levillaina81a3742018-01-04 13:11:52 +0000270 } catch (OutOfMemoryError ignored) {
271 // The call to semaphore.acquire() above may trigger an OOME,
272 // despite the care taken doing some warm-up by forcing
273 // ahead-of-time initialization of classes used by the Semaphore
274 // class (see forceTransitiveClassInitialization below).
275 // For instance, one of the code paths executes
276 // AbstractQueuedSynchronizer.addWaiter, which allocates an
277 // AbstractQueuedSynchronizer$Node (see b/67730573).
278 // In that case, just ignore the OOME and continue.
Roland Levillain0c806b12017-08-15 15:09:55 +0100279 } catch (InterruptedException ignored) {
280 } finally {
281 if (permitAcquired) {
282 semaphore.release();
283 }
284 }
285 return true;
286 }
287 }
288
289 private final static Map<Operation, Double> createDefaultFrequencyMap(Object lock,
290 Semaphore semaphore) {
Andreas Gampef2fdc732014-06-11 08:20:47 -0700291 Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>();
Roland Levillain0c806b12017-08-15 15:09:55 +0100292 frequencyMap.put(new OOM(), 0.005); // 1/200
293 frequencyMap.put(new SigQuit(), 0.095); // 19/200
294 frequencyMap.put(new Alloc(), 0.225); // 45/200
295 frequencyMap.put(new LargeAlloc(), 0.05); // 10/200
296 frequencyMap.put(new StackTrace(), 0.1); // 20/200
297 frequencyMap.put(new Exit(), 0.225); // 45/200
298 frequencyMap.put(new Sleep(), 0.125); // 25/200
299 frequencyMap.put(new TimedWait(lock), 0.05); // 10/200
300 frequencyMap.put(new Wait(lock), 0.075); // 15/200
301 frequencyMap.put(new QueuedWait(semaphore), 0.05); // 10/200
Andreas Gampef2fdc732014-06-11 08:20:47 -0700302
303 return frequencyMap;
304 }
305
306 private final static Map<Operation, Double> createLockFrequencyMap(Object lock) {
307 Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>();
Roland Levillain0c806b12017-08-15 15:09:55 +0100308 frequencyMap.put(new Sleep(), 0.2); // 40/200
309 frequencyMap.put(new TimedWait(lock), 0.2); // 40/200
310 frequencyMap.put(new Wait(lock), 0.2); // 40/200
311 frequencyMap.put(new SyncAndWork(lock), 0.4); // 80/200
Andreas Gampef2fdc732014-06-11 08:20:47 -0700312
313 return frequencyMap;
314 }
315
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700316 public static void main(String[] args) throws Exception {
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100317 System.loadLibrary(args[0]);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700318 parseAndRun(args);
319 }
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700320
Andreas Gampef2fdc732014-06-11 08:20:47 -0700321 private static Map<Operation, Double> updateFrequencyMap(Map<Operation, Double> in,
Roland Levillain0c806b12017-08-15 15:09:55 +0100322 Object lock, Semaphore semaphore, String arg) {
Andreas Gampef2fdc732014-06-11 08:20:47 -0700323 String split[] = arg.split(":");
324 if (split.length != 2) {
325 throw new IllegalArgumentException("Can't split argument " + arg);
326 }
327 double d;
328 try {
329 d = Double.parseDouble(split[1]);
330 } catch (Exception e) {
331 throw new IllegalArgumentException(e);
332 }
333 if (d < 0) {
334 throw new IllegalArgumentException(arg + ": value must be >= 0.");
335 }
336 Operation op = null;
337 if (split[0].equals("-oom")) {
338 op = new OOM();
339 } else if (split[0].equals("-sigquit")) {
340 op = new SigQuit();
341 } else if (split[0].equals("-alloc")) {
342 op = new Alloc();
Andreas Gampe059e6272016-01-05 12:57:56 -0800343 } else if (split[0].equals("-largealloc")) {
344 op = new LargeAlloc();
Andreas Gampef2fdc732014-06-11 08:20:47 -0700345 } else if (split[0].equals("-stacktrace")) {
346 op = new StackTrace();
347 } else if (split[0].equals("-exit")) {
348 op = new Exit();
349 } else if (split[0].equals("-sleep")) {
350 op = new Sleep();
351 } else if (split[0].equals("-wait")) {
352 op = new Wait(lock);
353 } else if (split[0].equals("-timedwait")) {
354 op = new TimedWait(lock);
Roland Levillain0c806b12017-08-15 15:09:55 +0100355 } else if (split[0].equals("-syncandwork")) {
356 op = new SyncAndWork(lock);
357 } else if (split[0].equals("-queuedwait")) {
358 op = new QueuedWait(semaphore);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700359 } else {
360 throw new IllegalArgumentException("Unknown arg " + arg);
361 }
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700362
Andreas Gampef2fdc732014-06-11 08:20:47 -0700363 if (in == null) {
364 in = new HashMap<Operation, Double>();
365 }
366 in.put(op, d);
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700367
Andreas Gampef2fdc732014-06-11 08:20:47 -0700368 return in;
369 }
370
371 private static void normalize(Map<Operation, Double> map) {
372 double sum = 0;
373 for (Double d : map.values()) {
374 sum += d;
375 }
376 if (sum == 0) {
377 throw new RuntimeException("No elements!");
378 }
379 if (sum != 1.0) {
380 // Avoid ConcurrentModificationException.
381 Set<Operation> tmp = new HashSet<>(map.keySet());
382 for (Operation op : tmp) {
383 map.put(op, map.get(op) / sum);
384 }
385 }
386 }
387
388 public static void parseAndRun(String[] args) throws Exception {
389 int numberOfThreads = -1;
Man Caodef3fcd2015-08-10 15:51:27 -0700390 int numberOfDaemons = -1;
Andreas Gampef2fdc732014-06-11 08:20:47 -0700391 int totalOperations = -1;
392 int operationsPerThread = -1;
Roland Levillain0c806b12017-08-15 15:09:55 +0100393 int permits = -1;
Andreas Gampef2fdc732014-06-11 08:20:47 -0700394 Object lock = new Object();
395 Map<Operation, Double> frequencyMap = null;
396 boolean dumpMap = false;
397
398 if (args != null) {
Mathieu Chartier031768a2015-08-27 10:25:02 -0700399 // args[0] is libarttest
400 for (int i = 1; i < args.length; i++) {
Andreas Gampef2fdc732014-06-11 08:20:47 -0700401 if (args[i].equals("-n")) {
402 i++;
403 numberOfThreads = Integer.parseInt(args[i]);
Man Caodef3fcd2015-08-10 15:51:27 -0700404 } else if (args[i].equals("-d")) {
405 i++;
406 numberOfDaemons = Integer.parseInt(args[i]);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700407 } else if (args[i].equals("-o")) {
408 i++;
409 totalOperations = Integer.parseInt(args[i]);
410 } else if (args[i].equals("-t")) {
411 i++;
412 operationsPerThread = Integer.parseInt(args[i]);
Roland Levillain0c806b12017-08-15 15:09:55 +0100413 } else if (args[i].equals("-p")) {
414 i++;
415 permits = Integer.parseInt(args[i]);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700416 } else if (args[i].equals("--locks-only")) {
417 lock = new Object();
418 frequencyMap = createLockFrequencyMap(lock);
419 } else if (args[i].equals("--dumpmap")) {
420 dumpMap = true;
421 } else {
Roland Levillain0c806b12017-08-15 15:09:55 +0100422 Semaphore semaphore = getSemaphore(permits);
423 frequencyMap = updateFrequencyMap(frequencyMap, lock, semaphore, args[i]);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700424 }
425 }
426 }
427
428 if (totalOperations != -1 && operationsPerThread != -1) {
429 throw new IllegalArgumentException(
430 "Specified both totalOperations and operationsPerThread");
431 }
432
433 if (numberOfThreads == -1) {
434 numberOfThreads = 5;
435 }
436
Man Caodef3fcd2015-08-10 15:51:27 -0700437 if (numberOfDaemons == -1) {
438 numberOfDaemons = 3;
439 }
440
Andreas Gampef2fdc732014-06-11 08:20:47 -0700441 if (totalOperations == -1) {
442 totalOperations = 1000;
443 }
444
445 if (operationsPerThread == -1) {
446 operationsPerThread = totalOperations/numberOfThreads;
447 }
448
449 if (frequencyMap == null) {
Roland Levillain0c806b12017-08-15 15:09:55 +0100450 Semaphore semaphore = getSemaphore(permits);
451 frequencyMap = createDefaultFrequencyMap(lock, semaphore);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700452 }
453 normalize(frequencyMap);
454
455 if (dumpMap) {
456 System.out.println(frequencyMap);
457 }
458
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100459 try {
460 runTest(numberOfThreads, numberOfDaemons, operationsPerThread, lock, frequencyMap);
461 } catch (Throwable t) {
462 // In this case, the output should not contain all the required
463 // "Finishing worker" lines.
464 Main.printThrowable(t);
465 }
Andreas Gampef2fdc732014-06-11 08:20:47 -0700466 }
467
Roland Levillain0c806b12017-08-15 15:09:55 +0100468 private static Semaphore getSemaphore(int permits) {
469 if (permits == -1) {
470 // Default number of permits.
471 permits = 3;
472 }
473
Roland Levillainee98a132017-10-11 17:17:55 +0100474 Semaphore semaphore = new Semaphore(permits, /* fair */ true);
475 forceTransitiveClassInitialization(semaphore, permits);
476 return semaphore;
477 }
478
479 // Force ahead-of-time initialization of classes used by Semaphore
480 // code. Try to exercise all code paths likely to be taken during
481 // the actual test later (including having a thread blocking on
482 // the semaphore trying to acquire a permit), so that we increase
483 // the chances to initialize all classes indirectly used by
484 // QueuedWait (e.g. AbstractQueuedSynchronizer$Node).
485 private static void forceTransitiveClassInitialization(Semaphore semaphore, final int permits) {
486 // Ensure `semaphore` has the expected number of permits
487 // before we start.
488 assert semaphore.availablePermits() == permits;
489
490 // Let the main (current) thread acquire all permits from
491 // `semaphore`. Then create an auxiliary thread acquiring a
492 // permit from `semaphore`, blocking because none is
493 // available. Have the main thread release one permit, thus
494 // unblocking the second thread.
495
496 // Auxiliary thread.
497 Thread auxThread = new Thread("Aux") {
498 public void run() {
499 try {
500 // Try to acquire one permit, and block until
501 // that permit is released by the main thread.
502 semaphore.acquire();
503 // When unblocked, release the acquired permit
504 // immediately.
505 semaphore.release();
506 } catch (InterruptedException ignored) {
507 throw new RuntimeException("Test set up failed in auxiliary thread");
508 }
509 }
510 };
511
512 // Main thread.
513 try {
514 // Acquire all permits.
515 semaphore.acquire(permits);
516 // Start the auxiliary thread and have it try to acquire a
517 // permit.
518 auxThread.start();
519 // Synchronization: Wait until the auxiliary thread is
520 // blocked trying to acquire a permit from `semaphore`.
521 while (!semaphore.hasQueuedThreads()) {
522 Thread.sleep(100);
523 }
524 // Release one permit, thus unblocking `auxThread` and let
525 // it acquire a permit.
526 semaphore.release();
527 // Synchronization: Wait for the auxiliary thread to die.
528 auxThread.join();
529 // Release remaining permits.
530 semaphore.release(permits - 1);
531
532 // Verify that all permits have been released.
533 assert semaphore.availablePermits() == permits;
534 } catch (InterruptedException ignored) {
535 throw new RuntimeException("Test set up failed in main thread");
536 }
Roland Levillain0c806b12017-08-15 15:09:55 +0100537 }
538
Man Caodef3fcd2015-08-10 15:51:27 -0700539 public static void runTest(final int numberOfThreads, final int numberOfDaemons,
540 final int operationsPerThread, final Object lock,
541 Map<Operation, Double> frequencyMap) throws Exception {
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100542 final Thread mainThread = Thread.currentThread();
543 final Barrier startBarrier = new Barrier(numberOfThreads + numberOfDaemons + 1);
544
Man Caodef3fcd2015-08-10 15:51:27 -0700545 // Each normal thread is going to do operationsPerThread
546 // operations. Each daemon thread will loop over all
547 // the operations and will not stop.
548 // The distribution of operations is determined by
Roland Levillain0c806b12017-08-15 15:09:55 +0100549 // the frequencyMap values. We fill out an Operation[]
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700550 // for each thread with the operations it is to perform. The
551 // Operation[] is shuffled so that there is more random
552 // interactions between the threads.
553
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700554 // Fill in the Operation[] array for each thread by laying
555 // down references to operation according to their desired
556 // frequency.
Man Caodef3fcd2015-08-10 15:51:27 -0700557 // The first numberOfThreads elements are normal threads, the last
558 // numberOfDaemons elements are daemon threads.
559 final Main[] threadStresses = new Main[numberOfThreads + numberOfDaemons];
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700560 for (int t = 0; t < threadStresses.length; t++) {
561 Operation[] operations = new Operation[operationsPerThread];
562 int o = 0;
563 LOOP:
564 while (true) {
Andreas Gampef2fdc732014-06-11 08:20:47 -0700565 for (Operation op : frequencyMap.keySet()) {
566 int freq = (int)(frequencyMap.get(op) * operationsPerThread);
567 for (int f = 0; f < freq; f++) {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700568 if (o == operations.length) {
569 break LOOP;
570 }
571 operations[o] = op;
572 o++;
573 }
574 }
575 }
Man Caodef3fcd2015-08-10 15:51:27 -0700576 // Randomize the operation order
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700577 Collections.shuffle(Arrays.asList(operations));
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100578 threadStresses[t] = (t < numberOfThreads)
579 ? new Main(lock, t, operations)
580 : new Daemon(lock, t, operations, mainThread, startBarrier);
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700581 }
582
Andreas Gampef2fdc732014-06-11 08:20:47 -0700583 // Enable to dump operation counts per thread to make sure its
Roland Levillain0c806b12017-08-15 15:09:55 +0100584 // sane compared to frequencyMap.
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700585 if (DEBUG) {
586 for (int t = 0; t < threadStresses.length; t++) {
Andreas Gampef2fdc732014-06-11 08:20:47 -0700587 Operation[] operations = threadStresses[t].operations;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700588 Map<Operation, Integer> distribution = new HashMap<Operation, Integer>();
589 for (Operation operation : operations) {
590 Integer ops = distribution.get(operation);
591 if (ops == null) {
592 ops = 1;
593 } else {
594 ops++;
595 }
596 distribution.put(operation, ops);
597 }
598 System.out.println("Distribution for " + t);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700599 for (Operation op : frequencyMap.keySet()) {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700600 System.out.println(op + " = " + distribution.get(op));
601 }
602 }
603 }
604
605 // Create the runners for each thread. The runner Thread
Roland Levillain0c806b12017-08-15 15:09:55 +0100606 // ensures that thread that exit due to operation Exit will be
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700607 // restarted until they reach their desired
608 // operationsPerThread.
609 Thread[] runners = new Thread[numberOfThreads];
610 for (int r = 0; r < runners.length; r++) {
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700611 final Main ts = threadStresses[r];
Mathieu Chartier5f51d4b2013-12-03 14:24:05 -0800612 runners[r] = new Thread("Runner thread " + r) {
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700613 final Main threadStress = ts;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700614 public void run() {
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100615 try {
616 int id = threadStress.id;
617 // No memory hungry task are running yet, so println() should succeed.
618 System.out.println("Starting worker for " + id);
619 // Wait until all runners and daemons reach the starting point.
620 startBarrier.await();
621 // Run the stress tasks.
622 while (threadStress.nextOperation < operationsPerThread) {
Mathieu Chartier72e36d82015-09-17 20:46:56 -0700623 try {
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100624 Thread thread = new Thread(ts, "Worker thread " + id);
625 thread.start();
Mathieu Chartier72e36d82015-09-17 20:46:56 -0700626 thread.join();
Mathieu Chartier72e36d82015-09-17 20:46:56 -0700627
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100628 if (DEBUG) {
629 System.out.println(
630 "Thread exited for " + id + " with " +
631 (operationsPerThread - threadStress.nextOperation) +
632 " operations remaining.");
633 }
634 } catch (OutOfMemoryError e) {
635 // Ignore OOME since we need to print "Finishing worker"
636 // for the test to pass. This OOM can come from creating
637 // the Thread or from the DEBUG output.
638 // Note that the Thread creation may fail repeatedly,
639 // preventing the runner from making any progress,
640 // especially if the number of daemons is too high.
641 }
Mathieu Chartier9d3c3fc2015-08-18 11:42:03 -0700642 }
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100643 // Print "Finishing worker" through JNI to avoid OOME.
644 Main.printString(Main.finishingWorkerMessage);
645 } catch (Throwable t) {
646 Main.printThrowable(t);
647 // Interrupt the main thread, so that it can orderly shut down
648 // instead of waiting indefinitely for some Barrier.
649 mainThread.interrupt();
Mathieu Chartier72e36d82015-09-17 20:46:56 -0700650 }
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700651 }
652 };
653 }
654
655 // The notifier thread is a daemon just loops forever to wake
Roland Levillain0c806b12017-08-15 15:09:55 +0100656 // up threads in operation Wait.
Andreas Gampef2fdc732014-06-11 08:20:47 -0700657 if (lock != null) {
658 Thread notifier = new Thread("Notifier") {
659 public void run() {
660 while (true) {
661 synchronized (lock) {
662 lock.notifyAll();
663 }
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700664 }
665 }
Andreas Gampef2fdc732014-06-11 08:20:47 -0700666 };
667 notifier.setDaemon(true);
668 notifier.start();
669 }
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700670
Man Caodef3fcd2015-08-10 15:51:27 -0700671 // Create and start the daemon threads.
672 for (int r = 0; r < numberOfDaemons; r++) {
673 Main daemon = threadStresses[numberOfThreads + r];
674 Thread t = new Thread(daemon, "Daemon thread " + daemon.id);
675 t.setDaemon(true);
676 t.start();
677 }
678
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700679 for (int r = 0; r < runners.length; r++) {
680 runners[r].start();
681 }
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100682 // Wait for all threads to reach the starting point.
683 startBarrier.await();
684 // Wait for runners to finish.
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700685 for (int r = 0; r < runners.length; r++) {
686 runners[r].join();
687 }
688 }
689
Man Caodef3fcd2015-08-10 15:51:27 -0700690 protected final Operation[] operations;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700691 private final Object lock;
Man Caodef3fcd2015-08-10 15:51:27 -0700692 protected final int id;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700693
694 private int nextOperation;
695
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700696 private Main(Object lock, int id, Operation[] operations) {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700697 this.lock = lock;
698 this.id = id;
699 this.operations = operations;
700 }
701
702 public void run() {
703 try {
704 if (DEBUG) {
705 System.out.println("Starting ThreadStress " + id);
706 }
707 while (nextOperation < operations.length) {
708 Operation operation = operations[nextOperation];
709 if (DEBUG) {
710 System.out.println("ThreadStress " + id
711 + " operation " + nextOperation
712 + " is " + operation);
713 }
714 nextOperation++;
Andreas Gampef2fdc732014-06-11 08:20:47 -0700715 if (!operation.perform()) {
716 return;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700717 }
718 }
Brian Carlstrom4514d3c2011-10-21 17:01:31 -0700719 } finally {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700720 if (DEBUG) {
721 System.out.println("Finishing ThreadStress for " + id);
722 }
723 }
724 }
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700725
Man Caodef3fcd2015-08-10 15:51:27 -0700726 private static class Daemon extends Main {
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100727 private Daemon(Object lock,
728 int id,
729 Operation[] operations,
730 Thread mainThread,
731 Barrier startBarrier) {
Man Caodef3fcd2015-08-10 15:51:27 -0700732 super(lock, id, operations);
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100733 this.mainThread = mainThread;
734 this.startBarrier = startBarrier;
Man Caodef3fcd2015-08-10 15:51:27 -0700735 }
736
737 public void run() {
738 try {
739 if (DEBUG) {
740 System.out.println("Starting ThreadStress Daemon " + id);
741 }
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100742 startBarrier.await();
743 try {
744 int i = 0;
745 while (true) {
746 Operation operation = operations[i];
747 if (DEBUG) {
748 System.out.println("ThreadStress Daemon " + id
749 + " operation " + i
750 + " is " + operation);
751 }
Roland Levillain0c806b12017-08-15 15:09:55 +0100752 // Ignore the result of the performed operation, making
753 // Exit.perform() essentially a no-op for daemon threads.
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100754 operation.perform();
755 i = (i + 1) % operations.length;
Man Caodef3fcd2015-08-10 15:51:27 -0700756 }
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100757 } catch (OutOfMemoryError e) {
758 // Catch OutOfMemoryErrors since these can cause the test to fail it they print
759 // the stack trace after "Finishing worker". Note that operations should catch
760 // their own OOME, this guards only agains OOME in the DEBUG output.
Man Caodef3fcd2015-08-10 15:51:27 -0700761 }
Man Caodef3fcd2015-08-10 15:51:27 -0700762 if (DEBUG) {
763 System.out.println("Finishing ThreadStress Daemon for " + id);
764 }
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100765 } catch (Throwable t) {
766 Main.printThrowable(t);
767 // Interrupt the main thread, so that it can orderly shut down
768 // instead of waiting indefinitely for some Barrier.
769 mainThread.interrupt();
Man Caodef3fcd2015-08-10 15:51:27 -0700770 }
771 }
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100772
773 final Thread mainThread;
774 final Barrier startBarrier;
Man Caodef3fcd2015-08-10 15:51:27 -0700775 }
776
Vladimir Marko3ea7c9e2016-06-21 18:21:53 +0100777 // Note: java.util.concurrent.CyclicBarrier.await() allocates memory and may throw OOM.
778 // That is highly undesirable in this test, so we use our own simple barrier class.
779 // The only memory allocation that can happen here is the lock inflation which uses
780 // a native allocation. As such, it should succeed even if the Java heap is full.
781 // If the native allocation surprisingly fails, the program shall abort().
782 private static class Barrier {
783 public Barrier(int initialCount) {
784 count = initialCount;
785 }
786
787 public synchronized void await() throws InterruptedException {
788 --count;
789 if (count != 0) {
790 do {
791 wait();
792 } while (count != 0); // Check for spurious wakeup.
793 } else {
794 notifyAll();
795 }
796 }
797
798 private int count;
799 }
800
801 // Printing a String/Throwable through JNI requires only native memory and space
802 // in the local reference table, so it should succeed even if the Java heap is full.
803 private static native void printString(String s);
804 private static native void printThrowable(Throwable t);
805
806 static final String finishingWorkerMessage;
807 static final String errnoExceptionName;
808 static {
809 // We pre-allocate the strings in class initializer to avoid const-string
810 // instructions in code using these strings later as they may throw OOME.
811 finishingWorkerMessage = "Finishing worker\n";
812 errnoExceptionName = "ErrnoException";
813 }
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700814}