1 /*
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 
17 import dalvik.system.VMRuntime;
18 
19 import java.lang.reflect.*;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28 import java.util.concurrent.Semaphore;
29 import java.util.concurrent.locks.LockSupport;
30 
31 // Run on host with:
32 //   javac ThreadTest.java && java ThreadStress && rm *.class
33 // Through run-test:
34 //   test/run-test {run-test-args} 004-ThreadStress [Main {ThreadStress-args}]
35 //   (It is important to pass Main if you want to give parameters...)
36 //
37 // ThreadStress command line parameters:
38 //    -n X .............. number of threads
39 //    -d X .............. number of daemon threads
40 //    -o X .............. number of overall operations
41 //    -t X .............. number of operations per thread
42 //    -p X .............. number of permits granted by semaphore
43 //    --dumpmap ......... print the frequency map
44 //    --locks-only ...... select a pre-set frequency map with lock-related operations only
45 //    --allocs-only ..... select a pre-set frequency map with allocation-related operations only
46 //    -oom:X ............ frequency of OOM (double)
47 //    -sigquit:X ........ frequency of SigQuit (double)
48 //    -alloc:X .......... frequency of Alloc (double)
49 //    -largealloc:X ..... frequency of LargeAlloc (double)
50 //    -nonmovingalloc:X.. frequency of NonMovingAlloc (double)
51 //    -stacktrace:X ..... frequency of StackTrace (double)
52 //    -exit:X ........... frequency of Exit (double)
53 //    -sleep:X .......... frequency of Sleep (double)
54 //    -wait:X ........... frequency of Wait (double)
55 //    -timedwait:X ...... frequency of TimedWait (double)
56 //    -timedpark:X ...... frequency of TimedPark (double)
57 //    -syncandwork:X .... frequency of SyncAndWork (double)
58 //    -queuedwait:X ..... frequency of QueuedWait (double)
59 
60 public class Main implements Runnable {
61 
62     public static final boolean DEBUG = false;
63 
64     private static abstract class Operation {
65         /**
66          * Perform the action represented by this operation. Returns true if the thread should
67          * continue when executed by a runner (non-daemon) thread.
68          */
perform()69         public abstract boolean perform();
70     }
71 
72     private final static class OOM extends Operation {
73         private final static int ALLOC_SIZE = 1024;
74 
75         @Override
perform()76         public boolean perform() {
77             try {
78                 List<byte[]> l = new ArrayList<byte[]>();
79                 while (true) {
80                     l.add(new byte[ALLOC_SIZE]);
81                 }
82             } catch (OutOfMemoryError e) {
83             }
84             return true;
85         }
86     }
87 
88     private final static class SigQuit extends Operation {
89         private final static int sigquit;
90         private final static Method kill;
91         private final static int pid;
92 
93         static {
94             int pidTemp = -1;
95             int sigquitTemp = -1;
96             Method killTemp = null;
97 
98             try {
99                 Class<?> osClass = Class.forName("android.system.Os");
100                 Method getpid = osClass.getDeclaredMethod("getpid");
101                 pidTemp = (Integer)getpid.invoke(null);
102 
103                 Class<?> osConstants = Class.forName("android.system.OsConstants");
104                 Field sigquitField = osConstants.getDeclaredField("SIGQUIT");
105                 sigquitTemp = (Integer)sigquitField.get(null);
106 
107                 killTemp = osClass.getDeclaredMethod("kill", int.class, int.class);
108             } catch (Exception e) {
109                 Main.printThrowable(e);
110             }
111 
112             pid = pidTemp;
113             sigquit = sigquitTemp;
114             kill = killTemp;
115         }
116 
117         @Override
perform()118         public boolean perform() {
119             try {
120                 kill.invoke(null, pid, sigquit);
121             } catch (OutOfMemoryError e) {
122             } catch (Exception e) {
123                 if (!e.getClass().getName().equals(Main.errnoExceptionName)) {
124                     Main.printThrowable(e);
125                 }
126             }
127             return true;
128         }
129     }
130 
131     private final static class Alloc extends Operation {
132         private final static int ALLOC_SIZE = 1024;  // Needs to be small enough to not be in LOS.
133         private final static int ALLOC_COUNT = 1024;
134 
135         @Override
perform()136         public boolean perform() {
137             try {
138                 List<byte[]> l = new ArrayList<byte[]>();
139                 for (int i = 0; i < ALLOC_COUNT; i++) {
140                     l.add(new byte[ALLOC_SIZE]);
141                 }
142             } catch (OutOfMemoryError e) {
143             }
144             return true;
145         }
146     }
147 
148     private final static class LargeAlloc extends Operation {
149         private final static int PAGE_SIZE = 4096;
150         private final static int PAGE_SIZE_MODIFIER = 10;  // Needs to be large enough for LOS.
151         private final static int ALLOC_COUNT = 100;
152 
153         @Override
perform()154         public boolean perform() {
155             try {
156                 List<byte[]> l = new ArrayList<byte[]>();
157                 for (int i = 0; i < ALLOC_COUNT; i++) {
158                     l.add(new byte[PAGE_SIZE_MODIFIER * PAGE_SIZE]);
159                 }
160             } catch (OutOfMemoryError e) {
161             }
162             return true;
163         }
164     }
165 
166   private final static class NonMovingAlloc extends Operation {
167         private final static int ALLOC_SIZE = 1024;  // Needs to be small enough to not be in LOS.
168         private final static int ALLOC_COUNT = 1024;
169         private final static VMRuntime runtime = VMRuntime.getRuntime();
170 
171         @Override
perform()172         public boolean perform() {
173             try {
174                 List<byte[]> l = new ArrayList<byte[]>();
175                 for (int i = 0; i < ALLOC_COUNT; i++) {
176                     l.add((byte[]) runtime.newNonMovableArray(byte.class, ALLOC_SIZE));
177                 }
178             } catch (OutOfMemoryError e) {
179             }
180             return true;
181         }
182     }
183 
184 
185     private final static class StackTrace extends Operation {
186         @Override
perform()187         public boolean perform() {
188             try {
189                 Thread.currentThread().getStackTrace();
190             } catch (OutOfMemoryError e) {
191             }
192             return true;
193         }
194     }
195 
196     private final static class Exit extends Operation {
197         @Override
perform()198         public boolean perform() {
199             return false;
200         }
201     }
202 
203     private final static class Sleep extends Operation {
204         private final static int SLEEP_TIME = 100;
205 
206         @Override
perform()207         public boolean perform() {
208             try {
209                 Thread.sleep(SLEEP_TIME);
210             } catch (InterruptedException ignored) {
211             }
212             return true;
213         }
214     }
215 
216     private final static class TimedWait extends Operation {
217         private final static int SLEEP_TIME = 100;
218 
219         private final Object lock;
220 
TimedWait(Object lock)221         public TimedWait(Object lock) {
222             this.lock = lock;
223         }
224 
225         @Override
perform()226         public boolean perform() {
227             synchronized (lock) {
228                 try {
229                     lock.wait(SLEEP_TIME, 0);
230                 } catch (InterruptedException ignored) {
231                 }
232             }
233             return true;
234         }
235     }
236 
237     private final static class Wait extends Operation {
238         private final Object lock;
239 
Wait(Object lock)240         public Wait(Object lock) {
241             this.lock = lock;
242         }
243 
244         @Override
perform()245         public boolean perform() {
246             synchronized (lock) {
247                 try {
248                     lock.wait();
249                 } catch (InterruptedException ignored) {
250                 }
251             }
252             return true;
253         }
254     }
255 
256     private final static class TimedPark extends Operation {
257         private final static int SLEEP_TIME = 100;
258 
TimedPark()259         public TimedPark() {}
260 
261         @Override
perform()262         public boolean perform() {
263             LockSupport.parkNanos(this, 100*1000000);
264             return true;
265         }
266     }
267 
268     private final static class SyncAndWork extends Operation {
269         private final Object lock;
270 
SyncAndWork(Object lock)271         public SyncAndWork(Object lock) {
272             this.lock = lock;
273         }
274 
275         @Override
perform()276         public boolean perform() {
277             synchronized (lock) {
278                 try {
279                     Thread.sleep((int)(Math.random() * 50 + 50));
280                 } catch (InterruptedException ignored) {
281                 }
282             }
283             return true;
284         }
285     }
286 
287     // An operation requiring the acquisition of a permit from a semaphore
288     // for its execution. This operation has been added to exercise
289     // java.util.concurrent.locks.AbstractQueuedSynchronizer, used in the
290     // implementation of java.util.concurrent.Semaphore. We use the latter,
291     // as the former is not supposed to be used directly (see b/63822989).
292     private final static class QueuedWait extends Operation {
293         private final static int SLEEP_TIME = 100;
294 
295         private final Semaphore semaphore;
296 
QueuedWait(Semaphore semaphore)297         public QueuedWait(Semaphore semaphore) {
298             this.semaphore = semaphore;
299         }
300 
301         @Override
perform()302         public boolean perform() {
303             boolean permitAcquired = false;
304             try {
305                 semaphore.acquire();
306                 permitAcquired = true;
307                 Thread.sleep(SLEEP_TIME);
308             } catch (OutOfMemoryError ignored) {
309               // The call to semaphore.acquire() above may trigger an OOME,
310               // despite the care taken doing some warm-up by forcing
311               // ahead-of-time initialization of classes used by the Semaphore
312               // class (see forceTransitiveClassInitialization below).
313               // For instance, one of the code paths executes
314               // AbstractQueuedSynchronizer.addWaiter, which allocates an
315               // AbstractQueuedSynchronizer$Node (see b/67730573).
316               // In that case, just ignore the OOME and continue.
317             } catch (InterruptedException ignored) {
318             } finally {
319                 if (permitAcquired) {
320                     semaphore.release();
321                 }
322             }
323             return true;
324         }
325     }
326 
createDefaultFrequencyMap(Object lock, Semaphore semaphore)327     private final static Map<Operation, Double> createDefaultFrequencyMap(Object lock,
328             Semaphore semaphore) {
329         Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>();
330         frequencyMap.put(new OOM(), 0.005);                   //   1/200
331         frequencyMap.put(new SigQuit(), 0.095);               //  19/200
332         frequencyMap.put(new Alloc(), 0.2);                   //  40/200
333         frequencyMap.put(new LargeAlloc(), 0.05);             //  10/200
334         frequencyMap.put(new NonMovingAlloc(), 0.025);        //   5/200
335         frequencyMap.put(new StackTrace(), 0.1);              //  20/200
336         frequencyMap.put(new Exit(), 0.225);                  //  45/200
337         frequencyMap.put(new Sleep(), 0.075);                 //  15/200
338         frequencyMap.put(new TimedPark(), 0.05);              //  10/200
339         frequencyMap.put(new TimedWait(lock), 0.05);          //  10/200
340         frequencyMap.put(new Wait(lock), 0.075);              //  15/200
341         frequencyMap.put(new QueuedWait(semaphore), 0.05);    //  10/200
342 
343         return frequencyMap;
344     }
345 
createAllocFrequencyMap()346     private final static Map<Operation, Double> createAllocFrequencyMap() {
347         Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>();
348         frequencyMap.put(new Sleep(), 0.2);                   //  40/200
349         frequencyMap.put(new Alloc(), 0.575);                 // 115/200
350         frequencyMap.put(new LargeAlloc(), 0.15);             //  30/200
351         frequencyMap.put(new NonMovingAlloc(), 0.075);        //  15/200
352 
353         return frequencyMap;
354     }
355 
createLockFrequencyMap(Object lock)356     private final static Map<Operation, Double> createLockFrequencyMap(Object lock) {
357       Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>();
358       frequencyMap.put(new Sleep(), 0.2);                     //  40/200
359       frequencyMap.put(new TimedWait(lock), 0.1);             //  20/200
360       frequencyMap.put(new Wait(lock), 0.2);                  //  40/200
361       frequencyMap.put(new SyncAndWork(lock), 0.4);           //  80/200
362       frequencyMap.put(new TimedPark(), 0.1);                 //  20/200
363 
364       return frequencyMap;
365     }
366 
main(String[] args)367     public static void main(String[] args) throws Exception {
368         System.loadLibrary(args[0]);
369         parseAndRun(args);
370     }
371 
updateFrequencyMap(Map<Operation, Double> in, Object lock, Semaphore semaphore, String arg)372     private static Map<Operation, Double> updateFrequencyMap(Map<Operation, Double> in,
373             Object lock, Semaphore semaphore, String arg) {
374         String split[] = arg.split(":");
375         if (split.length != 2) {
376             throw new IllegalArgumentException("Can't split argument " + arg);
377         }
378         double d;
379         try {
380             d = Double.parseDouble(split[1]);
381         } catch (Exception e) {
382             throw new IllegalArgumentException(e);
383         }
384         if (d < 0) {
385             throw new IllegalArgumentException(arg + ": value must be >= 0.");
386         }
387         Operation op = null;
388         if (split[0].equals("-oom")) {
389             op = new OOM();
390         } else if (split[0].equals("-sigquit")) {
391             op = new SigQuit();
392         } else if (split[0].equals("-alloc")) {
393             op = new Alloc();
394         } else if (split[0].equals("-largealloc")) {
395             op = new LargeAlloc();
396         } else if (split[0].equals("-nonmovingalloc")) {
397             op = new NonMovingAlloc();
398         } else if (split[0].equals("-stacktrace")) {
399             op = new StackTrace();
400         } else if (split[0].equals("-exit")) {
401             op = new Exit();
402         } else if (split[0].equals("-sleep")) {
403             op = new Sleep();
404         } else if (split[0].equals("-wait")) {
405             op = new Wait(lock);
406         } else if (split[0].equals("-timedwait")) {
407             op = new TimedWait(lock);
408         } else if (split[0].equals("-timedpark")) {
409             op = new TimedPark();
410         } else if (split[0].equals("-syncandwork")) {
411             op = new SyncAndWork(lock);
412         } else if (split[0].equals("-queuedwait")) {
413             op = new QueuedWait(semaphore);
414         } else {
415             throw new IllegalArgumentException("Unknown arg " + arg);
416         }
417 
418         if (in == null) {
419             in = new HashMap<Operation, Double>();
420         }
421         in.put(op, d);
422 
423         return in;
424     }
425 
normalize(Map<Operation, Double> map)426     private static void normalize(Map<Operation, Double> map) {
427         double sum = 0;
428         for (Double d : map.values()) {
429             sum += d;
430         }
431         if (sum == 0) {
432             throw new RuntimeException("No elements!");
433         }
434         if (sum != 1.0) {
435             // Avoid ConcurrentModificationException.
436             Set<Operation> tmp = new HashSet<>(map.keySet());
437             for (Operation op : tmp) {
438                 map.put(op, map.get(op) / sum);
439             }
440         }
441     }
442 
parseAndRun(String[] args)443     public static void parseAndRun(String[] args) throws Exception {
444         int numberOfThreads = -1;
445         int numberOfDaemons = -1;
446         int totalOperations = -1;
447         int operationsPerThread = -1;
448         int permits = -1;
449         Object lock = new Object();
450         Map<Operation, Double> frequencyMap = null;
451         boolean dumpMap = false;
452 
453         if (args != null) {
454             // args[0] is libarttest
455             for (int i = 1; i < args.length; i++) {
456                 if (args[i].equals("-n")) {
457                     i++;
458                     numberOfThreads = Integer.parseInt(args[i]);
459                 } else if (args[i].equals("-d")) {
460                     i++;
461                     numberOfDaemons = Integer.parseInt(args[i]);
462                 } else if (args[i].equals("-o")) {
463                     i++;
464                     totalOperations = Integer.parseInt(args[i]);
465                 } else if (args[i].equals("-t")) {
466                     i++;
467                     operationsPerThread = Integer.parseInt(args[i]);
468                 } else if (args[i].equals("-p")) {
469                     i++;
470                     permits = Integer.parseInt(args[i]);
471                 } else if (args[i].equals("--locks-only")) {
472                     frequencyMap = createLockFrequencyMap(lock);
473                 } else if (args[i].equals("--allocs-only")) {
474                     frequencyMap = createAllocFrequencyMap();
475                 } else if (args[i].equals("--dumpmap")) {
476                     dumpMap = true;
477                 } else {
478                     // Processing an argument of the form "-<operation>:X"
479                     // (where X is a double value).
480                     Semaphore semaphore = getSemaphore(permits);
481                     frequencyMap = updateFrequencyMap(frequencyMap, lock, semaphore, args[i]);
482                 }
483             }
484         }
485 
486         if (totalOperations != -1 && operationsPerThread != -1) {
487             throw new IllegalArgumentException(
488                     "Specified both totalOperations and operationsPerThread");
489         }
490 
491         if (numberOfThreads == -1) {
492             numberOfThreads = 5;
493         }
494 
495         if (numberOfDaemons == -1) {
496             numberOfDaemons = 3;
497         }
498 
499         if (totalOperations == -1) {
500             totalOperations = 1000;
501         }
502 
503         if (operationsPerThread == -1) {
504             operationsPerThread = totalOperations/numberOfThreads;
505         }
506 
507         if (frequencyMap == null) {
508             Semaphore semaphore = getSemaphore(permits);
509             frequencyMap = createDefaultFrequencyMap(lock, semaphore);
510         }
511         normalize(frequencyMap);
512 
513         if (dumpMap) {
514             System.out.println(frequencyMap);
515         }
516 
517         try {
518             runTest(numberOfThreads, numberOfDaemons, operationsPerThread, lock, frequencyMap);
519         } catch (Throwable t) {
520             // In this case, the output should not contain all the required
521             // "Finishing worker" lines.
522             Main.printThrowable(t);
523         }
524     }
525 
getSemaphore(int permits)526     private static Semaphore getSemaphore(int permits) {
527         if (permits == -1) {
528             // Default number of permits.
529             permits = 3;
530         }
531 
532         Semaphore semaphore = new Semaphore(permits, /* fair */ true);
533         forceTransitiveClassInitialization(semaphore, permits);
534         return semaphore;
535     }
536 
537     // Force ahead-of-time initialization of classes used by Semaphore
538     // code. Try to exercise all code paths likely to be taken during
539     // the actual test later (including having a thread blocking on
540     // the semaphore trying to acquire a permit), so that we increase
541     // the chances to initialize all classes indirectly used by
542     // QueuedWait (e.g. AbstractQueuedSynchronizer$Node).
forceTransitiveClassInitialization(Semaphore semaphore, final int permits)543     private static void forceTransitiveClassInitialization(Semaphore semaphore, final int permits) {
544         // Ensure `semaphore` has the expected number of permits
545         // before we start.
546         assert semaphore.availablePermits() == permits;
547 
548         // Let the main (current) thread acquire all permits from
549         // `semaphore`. Then create an auxiliary thread acquiring a
550         // permit from `semaphore`, blocking because none is
551         // available. Have the main thread release one permit, thus
552         // unblocking the second thread.
553 
554         // Auxiliary thread.
555         Thread auxThread = new Thread("Aux") {
556             public void run() {
557                 try {
558                     // Try to acquire one permit, and block until
559                     // that permit is released by the main thread.
560                     semaphore.acquire();
561                     // When unblocked, release the acquired permit
562                     // immediately.
563                     semaphore.release();
564                 } catch (InterruptedException ignored) {
565                     throw new RuntimeException("Test set up failed in auxiliary thread");
566                 }
567             }
568         };
569 
570         // Main thread.
571         try {
572             // Acquire all permits.
573             semaphore.acquire(permits);
574             // Start the auxiliary thread and have it try to acquire a
575             // permit.
576             auxThread.start();
577             // Synchronization: Wait until the auxiliary thread is
578             // blocked trying to acquire a permit from `semaphore`.
579             while (!semaphore.hasQueuedThreads()) {
580                 Thread.sleep(100);
581             }
582             // Release one permit, thus unblocking `auxThread` and let
583             // it acquire a permit.
584             semaphore.release();
585             // Synchronization: Wait for the auxiliary thread to die.
586             auxThread.join();
587             // Release remaining permits.
588             semaphore.release(permits - 1);
589 
590             // Verify that all permits have been released.
591             assert semaphore.availablePermits() == permits;
592         } catch (InterruptedException ignored) {
593             throw new RuntimeException("Test set up failed in main thread");
594         }
595     }
596 
runTest(final int numberOfThreads, final int numberOfDaemons, final int operationsPerThread, final Object lock, Map<Operation, Double> frequencyMap)597     public static void runTest(final int numberOfThreads, final int numberOfDaemons,
598                                final int operationsPerThread, final Object lock,
599                                Map<Operation, Double> frequencyMap) throws Exception {
600         final Thread mainThread = Thread.currentThread();
601         final Barrier startBarrier = new Barrier(numberOfThreads + numberOfDaemons + 1);
602 
603         // Each normal thread is going to do operationsPerThread
604         // operations. Each daemon thread will loop over all
605         // the operations and will not stop.
606         // The distribution of operations is determined by
607         // the frequencyMap values. We fill out an Operation[]
608         // for each thread with the operations it is to perform. The
609         // Operation[] is shuffled so that there is more random
610         // interactions between the threads.
611 
612         // Fill in the Operation[] array for each thread by laying
613         // down references to operation according to their desired
614         // frequency.
615         // The first numberOfThreads elements are normal threads, the last
616         // numberOfDaemons elements are daemon threads.
617         final Main[] threadStresses = new Main[numberOfThreads + numberOfDaemons];
618         for (int t = 0; t < threadStresses.length; t++) {
619             Operation[] operations = new Operation[operationsPerThread];
620             int o = 0;
621             LOOP:
622             while (true) {
623                 for (Operation op : frequencyMap.keySet()) {
624                     int freq = (int)(frequencyMap.get(op) * operationsPerThread);
625                     for (int f = 0; f < freq; f++) {
626                         if (o == operations.length) {
627                             break LOOP;
628                         }
629                         operations[o] = op;
630                         o++;
631                     }
632                 }
633             }
634             // Randomize the operation order
635             Collections.shuffle(Arrays.asList(operations));
636             threadStresses[t] = (t < numberOfThreads)
637                     ? new Main(lock, t, operations)
638                     : new Daemon(lock, t, operations, mainThread, startBarrier);
639         }
640 
641         // Enable to dump operation counts per thread to see that it is
642         // commensurate with the frequencyMap.
643         if (DEBUG) {
644             for (int t = 0; t < threadStresses.length; t++) {
645                 Operation[] operations = threadStresses[t].operations;
646                 Map<Operation, Integer> distribution = new HashMap<Operation, Integer>();
647                 for (Operation operation : operations) {
648                     Integer ops = distribution.get(operation);
649                     if (ops == null) {
650                         ops = 1;
651                     } else {
652                         ops++;
653                     }
654                     distribution.put(operation, ops);
655                 }
656                 System.out.println("Distribution for " + t);
657                 for (Operation op : frequencyMap.keySet()) {
658                     System.out.println(op + " = " + distribution.get(op));
659                 }
660             }
661         }
662 
663         // Create the runners for each thread. The runner Thread
664         // ensures that thread that exit due to operation Exit will be
665         // restarted until they reach their desired
666         // operationsPerThread.
667         Thread[] runners = new Thread[numberOfThreads];
668         for (int r = 0; r < runners.length; r++) {
669             final Main ts = threadStresses[r];
670             runners[r] = new Thread("Runner thread " + r) {
671                 final Main threadStress = ts;
672                 public void run() {
673                     try {
674                         int id = threadStress.id;
675                         // No memory hungry task are running yet, so println() should succeed.
676                         System.out.println("Starting worker for " + id);
677                         // Wait until all runners and daemons reach the starting point.
678                         startBarrier.await();
679                         // Run the stress tasks.
680                         while (threadStress.nextOperation < operationsPerThread) {
681                             try {
682                                 Thread thread = new Thread(ts, "Worker thread " + id);
683                                 thread.start();
684                                 thread.join();
685 
686                                 if (DEBUG) {
687                                     System.out.println(
688                                         "Thread exited for " + id + " with " +
689                                         (operationsPerThread - threadStress.nextOperation) +
690                                         " operations remaining.");
691                                 }
692                             } catch (OutOfMemoryError e) {
693                                 // Ignore OOME since we need to print "Finishing worker"
694                                 // for the test to pass. This OOM can come from creating
695                                 // the Thread or from the DEBUG output.
696                                 // Note that the Thread creation may fail repeatedly,
697                                 // preventing the runner from making any progress,
698                                 // especially if the number of daemons is too high.
699                             }
700                         }
701                         // Print "Finishing worker" through JNI to avoid OOME.
702                         Main.printString(Main.finishingWorkerMessage);
703                     } catch (Throwable t) {
704                         Main.printThrowable(t);
705                         // Interrupt the main thread, so that it can orderly shut down
706                         // instead of waiting indefinitely for some Barrier.
707                         mainThread.interrupt();
708                     }
709                 }
710             };
711         }
712 
713         // The notifier thread is a daemon just loops forever to wake
714         // up threads in operations Wait and Park.
715         if (lock != null) {
716             Thread notifier = new Thread("Notifier") {
717                 public void run() {
718                     while (true) {
719                         synchronized (lock) {
720                             lock.notifyAll();
721                         }
722                         for (Thread runner : runners) {
723                           if (runner != null) {
724                             LockSupport.unpark(runner);
725                           }
726                         }
727                     }
728                 }
729             };
730             notifier.setDaemon(true);
731             notifier.start();
732         }
733 
734         // Create and start the daemon threads.
735         for (int r = 0; r < numberOfDaemons; r++) {
736             Main daemon = threadStresses[numberOfThreads + r];
737             Thread t = new Thread(daemon, "Daemon thread " + daemon.id);
738             t.setDaemon(true);
739             t.start();
740         }
741 
742         for (int r = 0; r < runners.length; r++) {
743             runners[r].start();
744         }
745         // Wait for all threads to reach the starting point.
746         startBarrier.await();
747         // Wait for runners to finish.
748         for (int r = 0; r < runners.length; r++) {
749             runners[r].join();
750         }
751     }
752 
753     protected final Operation[] operations;
754     private final Object lock;
755     protected final int id;
756 
757     private int nextOperation;
758 
Main(Object lock, int id, Operation[] operations)759     private Main(Object lock, int id, Operation[] operations) {
760         this.lock = lock;
761         this.id = id;
762         this.operations = operations;
763     }
764 
run()765     public void run() {
766         try {
767             if (DEBUG) {
768                 System.out.println("Starting ThreadStress " + id);
769             }
770             while (nextOperation < operations.length) {
771                 Operation operation = operations[nextOperation];
772                 if (DEBUG) {
773                     System.out.println("ThreadStress " + id
774                                        + " operation " + nextOperation
775                                        + " is " + operation);
776                 }
777                 nextOperation++;
778                 if (!operation.perform()) {
779                     return;
780                 }
781             }
782         } finally {
783             if (DEBUG) {
784                 System.out.println("Finishing ThreadStress for " + id);
785             }
786         }
787     }
788 
789     private static class Daemon extends Main {
Daemon(Object lock, int id, Operation[] operations, Thread mainThread, Barrier startBarrier)790         private Daemon(Object lock,
791                        int id,
792                        Operation[] operations,
793                        Thread mainThread,
794                        Barrier startBarrier) {
795             super(lock, id, operations);
796             this.mainThread = mainThread;
797             this.startBarrier = startBarrier;
798         }
799 
run()800         public void run() {
801             try {
802                 if (DEBUG) {
803                     System.out.println("Starting ThreadStress Daemon " + id);
804                 }
805                 startBarrier.await();
806                 try {
807                     int i = 0;
808                     while (true) {
809                         Operation operation = operations[i];
810                         if (DEBUG) {
811                             System.out.println("ThreadStress Daemon " + id
812                                                + " operation " + i
813                                                + " is " + operation);
814                         }
815                         // Ignore the result of the performed operation, making
816                         // Exit.perform() essentially a no-op for daemon threads.
817                         operation.perform();
818                         i = (i + 1) % operations.length;
819                     }
820                 } catch (OutOfMemoryError e) {
821                     // Catch OutOfMemoryErrors since these can cause the test to fail it they print
822                     // the stack trace after "Finishing worker". Note that operations should catch
823                     // their own OOME, this guards only agains OOME in the DEBUG output.
824                 }
825                 if (DEBUG) {
826                     System.out.println("Finishing ThreadStress Daemon for " + id);
827                 }
828             } catch (Throwable t) {
829                 Main.printThrowable(t);
830                 // Interrupt the main thread, so that it can orderly shut down
831                 // instead of waiting indefinitely for some Barrier.
832                 mainThread.interrupt();
833             }
834         }
835 
836         final Thread mainThread;
837         final Barrier startBarrier;
838     }
839 
840     // Note: java.util.concurrent.CyclicBarrier.await() allocates memory and may throw OOM.
841     // That is highly undesirable in this test, so we use our own simple barrier class.
842     // The only memory allocation that can happen here is the lock inflation which uses
843     // a native allocation. As such, it should succeed even if the Java heap is full.
844     // If the native allocation surprisingly fails, the program shall abort().
845     private static class Barrier {
Barrier(int initialCount)846         public Barrier(int initialCount) {
847             count = initialCount;
848         }
849 
await()850         public synchronized void await() throws InterruptedException {
851             --count;
852             if (count != 0) {
853                 do {
854                     wait();
855                 } while (count != 0);  // Check for spurious wakeup.
856             } else {
857                 notifyAll();
858             }
859         }
860 
861         private int count;
862     }
863 
864     // Printing a String/Throwable through JNI requires only native memory and space
865     // in the local reference table, so it should succeed even if the Java heap is full.
printString(String s)866     private static native void printString(String s);
printThrowable(Throwable t)867     private static native void printThrowable(Throwable t);
868 
869     static final String finishingWorkerMessage;
870     static final String errnoExceptionName;
871     static {
872         // We pre-allocate the strings in class initializer to avoid const-string
873         // instructions in code using these strings later as they may throw OOME.
874         finishingWorkerMessage = "Finishing worker\n";
875         errnoExceptionName = "ErrnoException";
876     }
877 }
878