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