• 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 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           */
perform()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
perform()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
perform()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
perform()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
perform()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
perform()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
perform()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
perform()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
perform()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  
TimedWait(Object lock)219          public TimedWait(Object lock) {
220              this.lock = lock;
221          }
222  
223          @Override
perform()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  
Wait(Object lock)238          public Wait(Object lock) {
239              this.lock = lock;
240          }
241  
242          @Override
perform()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  
SyncAndWork(Object lock)257          public SyncAndWork(Object lock) {
258              this.lock = lock;
259          }
260  
261          @Override
perform()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  
QueuedWait(Semaphore semaphore)283          public QueuedWait(Semaphore semaphore) {
284              this.semaphore = semaphore;
285          }
286  
287          @Override
perform()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  
createDefaultFrequencyMap(Object lock, Semaphore semaphore)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  
createAllocFrequencyMap()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  
createLockFrequencyMap(Object lock)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  
main(String[] args)353      public static void main(String[] args) throws Exception {
354          System.loadLibrary(args[0]);
355          parseAndRun(args);
356      }
357  
updateFrequencyMap(Map<Operation, Double> in, Object lock, Semaphore semaphore, String arg)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  
normalize(Map<Operation, Double> map)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  
parseAndRun(String[] args)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  
getSemaphore(int permits)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).
forceTransitiveClassInitialization(Semaphore semaphore, final int permits)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  
runTest(final int numberOfThreads, final int numberOfDaemons, final int operationsPerThread, final Object lock, Map<Operation, Double> frequencyMap)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  
Main(Object lock, int id, Operation[] operations)736      private Main(Object lock, int id, Operation[] operations) {
737          this.lock = lock;
738          this.id = id;
739          this.operations = operations;
740      }
741  
run()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 {
Daemon(Object lock, int id, Operation[] operations, Thread mainThread, Barrier startBarrier)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  
run()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 {
Barrier(int initialCount)823          public Barrier(int initialCount) {
824              count = initialCount;
825          }
826  
await()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.
printString(String s)843      private static native void printString(String s);
printThrowable(Throwable t)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