• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 java.lang.reflect.*;
18 import java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 
27 // Run on host with:
28 //   javac ThreadTest.java && java ThreadStress && rm *.class
29 // Through run-test:
30 //   test/run-test {run-test-args} 004-ThreadStress [Main {ThreadStress-args}]
31 //   (It is important to pass Main if you want to give parameters...)
32 //
33 // ThreadStress command line parameters:
34 //    -n X ............ number of threads
35 //    -d X ............ number of daemon threads
36 //    -o X ............ number of overall operations
37 //    -t X ............ number of operations per thread
38 //    --dumpmap ....... print the frequency map
39 //    -oom:X .......... frequency of OOM (double)
40 //    -alloc:X ........ frequency of Alloc
41 //    -stacktrace:X ... frequency of StackTrace
42 //    -exit:X ......... frequency of Exit
43 //    -sleep:X ........ frequency of Sleep
44 //    -wait:X ......... frequency of Wait
45 //    -timedwait:X .... frequency of TimedWait
46 
47 public class Main implements Runnable {
48 
49     public static final boolean DEBUG = false;
50 
51     private static abstract class Operation {
52         /**
53          * Perform the action represented by this operation. Returns true if the thread should
54          * continue.
55          */
perform()56         public abstract boolean perform();
57     }
58 
59     private final static class OOM extends Operation {
60         private final static int ALLOC_SIZE = 1024;
61 
62         @Override
perform()63         public boolean perform() {
64             try {
65                 List<byte[]> l = new ArrayList<byte[]>();
66                 while (true) {
67                     l.add(new byte[ALLOC_SIZE]);
68                 }
69             } catch (OutOfMemoryError e) {
70             }
71             return true;
72         }
73     }
74 
75     private final static class SigQuit extends Operation {
76         private final static int sigquit;
77         private final static Method kill;
78         private final static int pid;
79 
80         static {
81             int pidTemp = -1;
82             int sigquitTemp = -1;
83             Method killTemp = null;
84 
85             try {
86                 Class<?> osClass = Class.forName("android.system.Os");
87                 Method getpid = osClass.getDeclaredMethod("getpid");
88                 pidTemp = (Integer)getpid.invoke(null);
89 
90                 Class<?> osConstants = Class.forName("android.system.OsConstants");
91                 Field sigquitField = osConstants.getDeclaredField("SIGQUIT");
92                 sigquitTemp = (Integer)sigquitField.get(null);
93 
94                 killTemp = osClass.getDeclaredMethod("kill", int.class, int.class);
95             } catch (Exception e) {
96                 Main.printThrowable(e);
97             }
98 
99             pid = pidTemp;
100             sigquit = sigquitTemp;
101             kill = killTemp;
102         }
103 
104         @Override
perform()105         public boolean perform() {
106             try {
107                 kill.invoke(null, pid, sigquit);
108             } catch (OutOfMemoryError e) {
109             } catch (Exception e) {
110                 if (!e.getClass().getName().equals(Main.errnoExceptionName)) {
111                     Main.printThrowable(e);
112                 }
113             }
114             return true;
115         }
116     }
117 
118     private final static class Alloc extends Operation {
119         private final static int ALLOC_SIZE = 1024;  // Needs to be small enough to not be in LOS.
120         private final static int ALLOC_COUNT = 1024;
121 
122         @Override
perform()123         public boolean perform() {
124             try {
125                 List<byte[]> l = new ArrayList<byte[]>();
126                 for (int i = 0; i < ALLOC_COUNT; i++) {
127                     l.add(new byte[ALLOC_SIZE]);
128                 }
129             } catch (OutOfMemoryError e) {
130             }
131             return true;
132         }
133     }
134 
135     private final static class LargeAlloc extends Operation {
136         private final static int PAGE_SIZE = 4096;
137         private final static int PAGE_SIZE_MODIFIER = 10;  // Needs to be large enough for LOS.
138         private final static int ALLOC_COUNT = 100;
139 
140         @Override
perform()141         public boolean perform() {
142             try {
143                 List<byte[]> l = new ArrayList<byte[]>();
144                 for (int i = 0; i < ALLOC_COUNT; i++) {
145                     l.add(new byte[PAGE_SIZE_MODIFIER * PAGE_SIZE]);
146                 }
147             } catch (OutOfMemoryError e) {
148             }
149             return true;
150         }
151     }
152 
153     private final static class StackTrace extends Operation {
154         @Override
perform()155         public boolean perform() {
156             try {
157                 Thread.currentThread().getStackTrace();
158             } catch (OutOfMemoryError e) {
159             }
160             return true;
161         }
162     }
163 
164     private final static class Exit extends Operation {
165         @Override
perform()166         public boolean perform() {
167             return false;
168         }
169     }
170 
171     private final static class Sleep extends Operation {
172         private final static int SLEEP_TIME = 100;
173 
174         @Override
perform()175         public boolean perform() {
176             try {
177                 Thread.sleep(SLEEP_TIME);
178             } catch (InterruptedException ignored) {
179             }
180             return true;
181         }
182     }
183 
184     private final static class TimedWait extends Operation {
185         private final static int SLEEP_TIME = 100;
186 
187         private final Object lock;
188 
TimedWait(Object lock)189         public TimedWait(Object lock) {
190             this.lock = lock;
191         }
192 
193         @Override
perform()194         public boolean perform() {
195             synchronized (lock) {
196                 try {
197                     lock.wait(SLEEP_TIME, 0);
198                 } catch (InterruptedException ignored) {
199                 }
200             }
201             return true;
202         }
203     }
204 
205     private final static class Wait extends Operation {
206         private final Object lock;
207 
Wait(Object lock)208         public Wait(Object lock) {
209             this.lock = lock;
210         }
211 
212         @Override
perform()213         public boolean perform() {
214             synchronized (lock) {
215                 try {
216                     lock.wait();
217                 } catch (InterruptedException ignored) {
218                 }
219             }
220             return true;
221         }
222     }
223 
224     private final static class SyncAndWork extends Operation {
225         private final Object lock;
226 
SyncAndWork(Object lock)227         public SyncAndWork(Object lock) {
228             this.lock = lock;
229         }
230 
231         @Override
perform()232         public boolean perform() {
233             synchronized (lock) {
234                 try {
235                     Thread.sleep((int)(Math.random() * 50 + 50));
236                 } catch (InterruptedException ignored) {
237                 }
238             }
239             return true;
240         }
241     }
242 
createDefaultFrequencyMap(Object lock)243     private final static Map<Operation, Double> createDefaultFrequencyMap(Object lock) {
244         Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>();
245         frequencyMap.put(new OOM(), 0.005);             //  1/200
246         frequencyMap.put(new SigQuit(), 0.095);         // 19/200
247         frequencyMap.put(new Alloc(), 0.25);            // 50/200
248         frequencyMap.put(new LargeAlloc(), 0.05);       // 10/200
249         frequencyMap.put(new StackTrace(), 0.1);        // 20/200
250         frequencyMap.put(new Exit(), 0.25);             // 50/200
251         frequencyMap.put(new Sleep(), 0.125);           // 25/200
252         frequencyMap.put(new TimedWait(lock), 0.05);    // 10/200
253         frequencyMap.put(new Wait(lock), 0.075);        // 15/200
254 
255         return frequencyMap;
256     }
257 
createLockFrequencyMap(Object lock)258     private final static Map<Operation, Double> createLockFrequencyMap(Object lock) {
259       Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>();
260       frequencyMap.put(new Sleep(), 0.2);
261       frequencyMap.put(new TimedWait(lock), 0.2);
262       frequencyMap.put(new Wait(lock), 0.2);
263       frequencyMap.put(new SyncAndWork(lock), 0.4);
264 
265       return frequencyMap;
266     }
267 
main(String[] args)268     public static void main(String[] args) throws Exception {
269         System.loadLibrary(args[0]);
270         parseAndRun(args);
271     }
272 
updateFrequencyMap(Map<Operation, Double> in, Object lock, String arg)273     private static Map<Operation, Double> updateFrequencyMap(Map<Operation, Double> in,
274             Object lock, String arg) {
275         String split[] = arg.split(":");
276         if (split.length != 2) {
277             throw new IllegalArgumentException("Can't split argument " + arg);
278         }
279         double d;
280         try {
281             d = Double.parseDouble(split[1]);
282         } catch (Exception e) {
283             throw new IllegalArgumentException(e);
284         }
285         if (d < 0) {
286             throw new IllegalArgumentException(arg + ": value must be >= 0.");
287         }
288         Operation op = null;
289         if (split[0].equals("-oom")) {
290             op = new OOM();
291         } else if (split[0].equals("-sigquit")) {
292             op = new SigQuit();
293         } else if (split[0].equals("-alloc")) {
294             op = new Alloc();
295         } else if (split[0].equals("-largealloc")) {
296             op = new LargeAlloc();
297         } else if (split[0].equals("-stacktrace")) {
298             op = new StackTrace();
299         } else if (split[0].equals("-exit")) {
300             op = new Exit();
301         } else if (split[0].equals("-sleep")) {
302             op = new Sleep();
303         } else if (split[0].equals("-wait")) {
304             op = new Wait(lock);
305         } else if (split[0].equals("-timedwait")) {
306             op = new TimedWait(lock);
307         } else {
308             throw new IllegalArgumentException("Unknown arg " + arg);
309         }
310 
311         if (in == null) {
312             in = new HashMap<Operation, Double>();
313         }
314         in.put(op, d);
315 
316         return in;
317     }
318 
normalize(Map<Operation, Double> map)319     private static void normalize(Map<Operation, Double> map) {
320         double sum = 0;
321         for (Double d : map.values()) {
322             sum += d;
323         }
324         if (sum == 0) {
325             throw new RuntimeException("No elements!");
326         }
327         if (sum != 1.0) {
328             // Avoid ConcurrentModificationException.
329             Set<Operation> tmp = new HashSet<>(map.keySet());
330             for (Operation op : tmp) {
331                 map.put(op, map.get(op) / sum);
332             }
333         }
334     }
335 
parseAndRun(String[] args)336     public static void parseAndRun(String[] args) throws Exception {
337         int numberOfThreads = -1;
338         int numberOfDaemons = -1;
339         int totalOperations = -1;
340         int operationsPerThread = -1;
341         Object lock = new Object();
342         Map<Operation, Double> frequencyMap = null;
343         boolean dumpMap = false;
344 
345         if (args != null) {
346             // args[0] is libarttest
347             for (int i = 1; i < args.length; i++) {
348                 if (args[i].equals("-n")) {
349                     i++;
350                     numberOfThreads = Integer.parseInt(args[i]);
351                 } else if (args[i].equals("-d")) {
352                     i++;
353                     numberOfDaemons = Integer.parseInt(args[i]);
354                 } else if (args[i].equals("-o")) {
355                     i++;
356                     totalOperations = Integer.parseInt(args[i]);
357                 } else if (args[i].equals("-t")) {
358                     i++;
359                     operationsPerThread = Integer.parseInt(args[i]);
360                 } else if (args[i].equals("--locks-only")) {
361                     lock = new Object();
362                     frequencyMap = createLockFrequencyMap(lock);
363                 } else if (args[i].equals("--dumpmap")) {
364                     dumpMap = true;
365                 } else {
366                     frequencyMap = updateFrequencyMap(frequencyMap, lock, args[i]);
367                 }
368             }
369         }
370 
371         if (totalOperations != -1 && operationsPerThread != -1) {
372             throw new IllegalArgumentException(
373                     "Specified both totalOperations and operationsPerThread");
374         }
375 
376         if (numberOfThreads == -1) {
377             numberOfThreads = 5;
378         }
379 
380         if (numberOfDaemons == -1) {
381             numberOfDaemons = 3;
382         }
383 
384         if (totalOperations == -1) {
385             totalOperations = 1000;
386         }
387 
388         if (operationsPerThread == -1) {
389             operationsPerThread = totalOperations/numberOfThreads;
390         }
391 
392         if (frequencyMap == null) {
393             frequencyMap = createDefaultFrequencyMap(lock);
394         }
395         normalize(frequencyMap);
396 
397         if (dumpMap) {
398             System.out.println(frequencyMap);
399         }
400 
401         try {
402             runTest(numberOfThreads, numberOfDaemons, operationsPerThread, lock, frequencyMap);
403         } catch (Throwable t) {
404             // In this case, the output should not contain all the required
405             // "Finishing worker" lines.
406             Main.printThrowable(t);
407         }
408     }
409 
runTest(final int numberOfThreads, final int numberOfDaemons, final int operationsPerThread, final Object lock, Map<Operation, Double> frequencyMap)410     public static void runTest(final int numberOfThreads, final int numberOfDaemons,
411                                final int operationsPerThread, final Object lock,
412                                Map<Operation, Double> frequencyMap) throws Exception {
413         final Thread mainThread = Thread.currentThread();
414         final Barrier startBarrier = new Barrier(numberOfThreads + numberOfDaemons + 1);
415 
416         // Each normal thread is going to do operationsPerThread
417         // operations. Each daemon thread will loop over all
418         // the operations and will not stop.
419         // The distribution of operations is determined by
420         // the Operation.frequency values. We fill out an Operation[]
421         // for each thread with the operations it is to perform. The
422         // Operation[] is shuffled so that there is more random
423         // interactions between the threads.
424 
425         // Fill in the Operation[] array for each thread by laying
426         // down references to operation according to their desired
427         // frequency.
428         // The first numberOfThreads elements are normal threads, the last
429         // numberOfDaemons elements are daemon threads.
430         final Main[] threadStresses = new Main[numberOfThreads + numberOfDaemons];
431         for (int t = 0; t < threadStresses.length; t++) {
432             Operation[] operations = new Operation[operationsPerThread];
433             int o = 0;
434             LOOP:
435             while (true) {
436                 for (Operation op : frequencyMap.keySet()) {
437                     int freq = (int)(frequencyMap.get(op) * operationsPerThread);
438                     for (int f = 0; f < freq; f++) {
439                         if (o == operations.length) {
440                             break LOOP;
441                         }
442                         operations[o] = op;
443                         o++;
444                     }
445                 }
446             }
447             // Randomize the operation order
448             Collections.shuffle(Arrays.asList(operations));
449             threadStresses[t] = (t < numberOfThreads)
450                     ? new Main(lock, t, operations)
451                     : new Daemon(lock, t, operations, mainThread, startBarrier);
452         }
453 
454         // Enable to dump operation counts per thread to make sure its
455         // sane compared to Operation.frequency
456         if (DEBUG) {
457             for (int t = 0; t < threadStresses.length; t++) {
458                 Operation[] operations = threadStresses[t].operations;
459                 Map<Operation, Integer> distribution = new HashMap<Operation, Integer>();
460                 for (Operation operation : operations) {
461                     Integer ops = distribution.get(operation);
462                     if (ops == null) {
463                         ops = 1;
464                     } else {
465                         ops++;
466                     }
467                     distribution.put(operation, ops);
468                 }
469                 System.out.println("Distribution for " + t);
470                 for (Operation op : frequencyMap.keySet()) {
471                     System.out.println(op + " = " + distribution.get(op));
472                 }
473             }
474         }
475 
476         // Create the runners for each thread. The runner Thread
477         // ensures that thread that exit due to Operation.EXIT will be
478         // restarted until they reach their desired
479         // operationsPerThread.
480         Thread[] runners = new Thread[numberOfThreads];
481         for (int r = 0; r < runners.length; r++) {
482             final Main ts = threadStresses[r];
483             runners[r] = new Thread("Runner thread " + r) {
484                 final Main threadStress = ts;
485                 public void run() {
486                     try {
487                         int id = threadStress.id;
488                         // No memory hungry task are running yet, so println() should succeed.
489                         System.out.println("Starting worker for " + id);
490                         // Wait until all runners and daemons reach the starting point.
491                         startBarrier.await();
492                         // Run the stress tasks.
493                         while (threadStress.nextOperation < operationsPerThread) {
494                             try {
495                                 Thread thread = new Thread(ts, "Worker thread " + id);
496                                 thread.start();
497                                 thread.join();
498 
499                                 if (DEBUG) {
500                                     System.out.println(
501                                         "Thread exited for " + id + " with " +
502                                         (operationsPerThread - threadStress.nextOperation) +
503                                         " operations remaining.");
504                                 }
505                             } catch (OutOfMemoryError e) {
506                                 // Ignore OOME since we need to print "Finishing worker"
507                                 // for the test to pass. This OOM can come from creating
508                                 // the Thread or from the DEBUG output.
509                                 // Note that the Thread creation may fail repeatedly,
510                                 // preventing the runner from making any progress,
511                                 // especially if the number of daemons is too high.
512                             }
513                         }
514                         // Print "Finishing worker" through JNI to avoid OOME.
515                         Main.printString(Main.finishingWorkerMessage);
516                     } catch (Throwable t) {
517                         Main.printThrowable(t);
518                         // Interrupt the main thread, so that it can orderly shut down
519                         // instead of waiting indefinitely for some Barrier.
520                         mainThread.interrupt();
521                     }
522                 }
523             };
524         }
525 
526         // The notifier thread is a daemon just loops forever to wake
527         // up threads in Operation.WAIT
528         if (lock != null) {
529             Thread notifier = new Thread("Notifier") {
530                 public void run() {
531                     while (true) {
532                         synchronized (lock) {
533                             lock.notifyAll();
534                         }
535                     }
536                 }
537             };
538             notifier.setDaemon(true);
539             notifier.start();
540         }
541 
542         // Create and start the daemon threads.
543         for (int r = 0; r < numberOfDaemons; r++) {
544             Main daemon = threadStresses[numberOfThreads + r];
545             Thread t = new Thread(daemon, "Daemon thread " + daemon.id);
546             t.setDaemon(true);
547             t.start();
548         }
549 
550         for (int r = 0; r < runners.length; r++) {
551             runners[r].start();
552         }
553         // Wait for all threads to reach the starting point.
554         startBarrier.await();
555         // Wait for runners to finish.
556         for (int r = 0; r < runners.length; r++) {
557             runners[r].join();
558         }
559     }
560 
561     protected final Operation[] operations;
562     private final Object lock;
563     protected final int id;
564 
565     private int nextOperation;
566 
Main(Object lock, int id, Operation[] operations)567     private Main(Object lock, int id, Operation[] operations) {
568         this.lock = lock;
569         this.id = id;
570         this.operations = operations;
571     }
572 
run()573     public void run() {
574         try {
575             if (DEBUG) {
576                 System.out.println("Starting ThreadStress " + id);
577             }
578             while (nextOperation < operations.length) {
579                 Operation operation = operations[nextOperation];
580                 if (DEBUG) {
581                     System.out.println("ThreadStress " + id
582                                        + " operation " + nextOperation
583                                        + " is " + operation);
584                 }
585                 nextOperation++;
586                 if (!operation.perform()) {
587                     return;
588                 }
589             }
590         } finally {
591             if (DEBUG) {
592                 System.out.println("Finishing ThreadStress for " + id);
593             }
594         }
595     }
596 
597     private static class Daemon extends Main {
Daemon(Object lock, int id, Operation[] operations, Thread mainThread, Barrier startBarrier)598         private Daemon(Object lock,
599                        int id,
600                        Operation[] operations,
601                        Thread mainThread,
602                        Barrier startBarrier) {
603             super(lock, id, operations);
604             this.mainThread = mainThread;
605             this.startBarrier = startBarrier;
606         }
607 
run()608         public void run() {
609             try {
610                 if (DEBUG) {
611                     System.out.println("Starting ThreadStress Daemon " + id);
612                 }
613                 startBarrier.await();
614                 try {
615                     int i = 0;
616                     while (true) {
617                         Operation operation = operations[i];
618                         if (DEBUG) {
619                             System.out.println("ThreadStress Daemon " + id
620                                                + " operation " + i
621                                                + " is " + operation);
622                         }
623                         operation.perform();
624                         i = (i + 1) % operations.length;
625                     }
626                 } catch (OutOfMemoryError e) {
627                     // Catch OutOfMemoryErrors since these can cause the test to fail it they print
628                     // the stack trace after "Finishing worker". Note that operations should catch
629                     // their own OOME, this guards only agains OOME in the DEBUG output.
630                 }
631                 if (DEBUG) {
632                     System.out.println("Finishing ThreadStress Daemon for " + id);
633                 }
634             } catch (Throwable t) {
635                 Main.printThrowable(t);
636                 // Interrupt the main thread, so that it can orderly shut down
637                 // instead of waiting indefinitely for some Barrier.
638                 mainThread.interrupt();
639             }
640         }
641 
642         final Thread mainThread;
643         final Barrier startBarrier;
644     }
645 
646     // Note: java.util.concurrent.CyclicBarrier.await() allocates memory and may throw OOM.
647     // That is highly undesirable in this test, so we use our own simple barrier class.
648     // The only memory allocation that can happen here is the lock inflation which uses
649     // a native allocation. As such, it should succeed even if the Java heap is full.
650     // If the native allocation surprisingly fails, the program shall abort().
651     private static class Barrier {
Barrier(int initialCount)652         public Barrier(int initialCount) {
653             count = initialCount;
654         }
655 
await()656         public synchronized void await() throws InterruptedException {
657             --count;
658             if (count != 0) {
659                 do {
660                     wait();
661                 } while (count != 0);  // Check for spurious wakeup.
662             } else {
663                 notifyAll();
664             }
665         }
666 
667         private int count;
668     }
669 
670     // Printing a String/Throwable through JNI requires only native memory and space
671     // in the local reference table, so it should succeed even if the Java heap is full.
printString(String s)672     private static native void printString(String s);
printThrowable(Throwable t)673     private static native void printThrowable(Throwable t);
674 
675     static final String finishingWorkerMessage;
676     static final String errnoExceptionName;
677     static {
678         // We pre-allocate the strings in class initializer to avoid const-string
679         // instructions in code using these strings later as they may throw OOME.
680         finishingWorkerMessage = "Finishing worker\n";
681         errnoExceptionName = "ErrnoException";
682     }
683 }
684