summaryrefslogtreecommitdiff
path: root/test/004-ThreadStress/src-art/Main.java
diff options
context:
space:
mode:
Diffstat (limited to 'test/004-ThreadStress/src-art/Main.java')
-rw-r--r--test/004-ThreadStress/src-art/Main.java852
1 files changed, 852 insertions, 0 deletions
diff --git a/test/004-ThreadStress/src-art/Main.java b/test/004-ThreadStress/src-art/Main.java
new file mode 100644
index 0000000000..0d469fb8d9
--- /dev/null
+++ b/test/004-ThreadStress/src-art/Main.java
@@ -0,0 +1,852 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import dalvik.system.VMRuntime;
+
+import java.lang.reflect.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Semaphore;
+
+// Run on host with:
+// javac ThreadTest.java && java ThreadStress && rm *.class
+// Through run-test:
+// test/run-test {run-test-args} 004-ThreadStress [Main {ThreadStress-args}]
+// (It is important to pass Main if you want to give parameters...)
+//
+// ThreadStress command line parameters:
+// -n X .............. number of threads
+// -d X .............. number of daemon threads
+// -o X .............. number of overall operations
+// -t X .............. number of operations per thread
+// -p X .............. number of permits granted by semaphore
+// --dumpmap ......... print the frequency map
+// --locks-only ...... select a pre-set frequency map with lock-related operations only
+// --allocs-only ..... select a pre-set frequency map with allocation-related operations only
+// -oom:X ............ frequency of OOM (double)
+// -sigquit:X ........ frequency of SigQuit (double)
+// -alloc:X .......... frequency of Alloc (double)
+// -largealloc:X ..... frequency of LargeAlloc (double)
+// -nonmovingalloc:X.. frequency of NonMovingAlloc (double)
+// -stacktrace:X ..... frequency of StackTrace (double)
+// -exit:X ........... frequency of Exit (double)
+// -sleep:X .......... frequency of Sleep (double)
+// -wait:X ........... frequency of Wait (double)
+// -timedwait:X ...... frequency of TimedWait (double)
+// -syncandwork:X .... frequency of SyncAndWork (double)
+// -queuedwait:X ..... frequency of QueuedWait (double)
+
+public class Main implements Runnable {
+
+ public static final boolean DEBUG = false;
+
+ private static abstract class Operation {
+ /**
+ * Perform the action represented by this operation. Returns true if the thread should
+ * continue when executed by a runner (non-daemon) thread.
+ */
+ public abstract boolean perform();
+ }
+
+ private final static class OOM extends Operation {
+ private final static int ALLOC_SIZE = 1024;
+
+ @Override
+ public boolean perform() {
+ try {
+ List<byte[]> l = new ArrayList<byte[]>();
+ while (true) {
+ l.add(new byte[ALLOC_SIZE]);
+ }
+ } catch (OutOfMemoryError e) {
+ }
+ return true;
+ }
+ }
+
+ private final static class SigQuit extends Operation {
+ private final static int sigquit;
+ private final static Method kill;
+ private final static int pid;
+
+ static {
+ int pidTemp = -1;
+ int sigquitTemp = -1;
+ Method killTemp = null;
+
+ try {
+ Class<?> osClass = Class.forName("android.system.Os");
+ Method getpid = osClass.getDeclaredMethod("getpid");
+ pidTemp = (Integer)getpid.invoke(null);
+
+ Class<?> osConstants = Class.forName("android.system.OsConstants");
+ Field sigquitField = osConstants.getDeclaredField("SIGQUIT");
+ sigquitTemp = (Integer)sigquitField.get(null);
+
+ killTemp = osClass.getDeclaredMethod("kill", int.class, int.class);
+ } catch (Exception e) {
+ Main.printThrowable(e);
+ }
+
+ pid = pidTemp;
+ sigquit = sigquitTemp;
+ kill = killTemp;
+ }
+
+ @Override
+ public boolean perform() {
+ try {
+ kill.invoke(null, pid, sigquit);
+ } catch (OutOfMemoryError e) {
+ } catch (Exception e) {
+ if (!e.getClass().getName().equals(Main.errnoExceptionName)) {
+ Main.printThrowable(e);
+ }
+ }
+ return true;
+ }
+ }
+
+ private final static class Alloc extends Operation {
+ private final static int ALLOC_SIZE = 1024; // Needs to be small enough to not be in LOS.
+ private final static int ALLOC_COUNT = 1024;
+
+ @Override
+ public boolean perform() {
+ try {
+ List<byte[]> l = new ArrayList<byte[]>();
+ for (int i = 0; i < ALLOC_COUNT; i++) {
+ l.add(new byte[ALLOC_SIZE]);
+ }
+ } catch (OutOfMemoryError e) {
+ }
+ return true;
+ }
+ }
+
+ private final static class LargeAlloc extends Operation {
+ private final static int PAGE_SIZE = 4096;
+ private final static int PAGE_SIZE_MODIFIER = 10; // Needs to be large enough for LOS.
+ private final static int ALLOC_COUNT = 100;
+
+ @Override
+ public boolean perform() {
+ try {
+ List<byte[]> l = new ArrayList<byte[]>();
+ for (int i = 0; i < ALLOC_COUNT; i++) {
+ l.add(new byte[PAGE_SIZE_MODIFIER * PAGE_SIZE]);
+ }
+ } catch (OutOfMemoryError e) {
+ }
+ return true;
+ }
+ }
+
+ private final static class NonMovingAlloc extends Operation {
+ private final static int ALLOC_SIZE = 1024; // Needs to be small enough to not be in LOS.
+ private final static int ALLOC_COUNT = 1024;
+ private final static VMRuntime runtime = VMRuntime.getRuntime();
+
+ @Override
+ public boolean perform() {
+ try {
+ List<byte[]> l = new ArrayList<byte[]>();
+ for (int i = 0; i < ALLOC_COUNT; i++) {
+ l.add((byte[]) runtime.newNonMovableArray(byte.class, ALLOC_SIZE));
+ }
+ } catch (OutOfMemoryError e) {
+ }
+ return true;
+ }
+ }
+
+
+ private final static class StackTrace extends Operation {
+ @Override
+ public boolean perform() {
+ try {
+ Thread.currentThread().getStackTrace();
+ } catch (OutOfMemoryError e) {
+ }
+ return true;
+ }
+ }
+
+ private final static class Exit extends Operation {
+ @Override
+ public boolean perform() {
+ return false;
+ }
+ }
+
+ private final static class Sleep extends Operation {
+ private final static int SLEEP_TIME = 100;
+
+ @Override
+ public boolean perform() {
+ try {
+ Thread.sleep(SLEEP_TIME);
+ } catch (InterruptedException ignored) {
+ }
+ return true;
+ }
+ }
+
+ private final static class TimedWait extends Operation {
+ private final static int SLEEP_TIME = 100;
+
+ private final Object lock;
+
+ public TimedWait(Object lock) {
+ this.lock = lock;
+ }
+
+ @Override
+ public boolean perform() {
+ synchronized (lock) {
+ try {
+ lock.wait(SLEEP_TIME, 0);
+ } catch (InterruptedException ignored) {
+ }
+ }
+ return true;
+ }
+ }
+
+ private final static class Wait extends Operation {
+ private final Object lock;
+
+ public Wait(Object lock) {
+ this.lock = lock;
+ }
+
+ @Override
+ public boolean perform() {
+ synchronized (lock) {
+ try {
+ lock.wait();
+ } catch (InterruptedException ignored) {
+ }
+ }
+ return true;
+ }
+ }
+
+ private final static class SyncAndWork extends Operation {
+ private final Object lock;
+
+ public SyncAndWork(Object lock) {
+ this.lock = lock;
+ }
+
+ @Override
+ public boolean perform() {
+ synchronized (lock) {
+ try {
+ Thread.sleep((int)(Math.random() * 50 + 50));
+ } catch (InterruptedException ignored) {
+ }
+ }
+ return true;
+ }
+ }
+
+ // An operation requiring the acquisition of a permit from a semaphore
+ // for its execution. This operation has been added to exercise
+ // java.util.concurrent.locks.AbstractQueuedSynchronizer, used in the
+ // implementation of java.util.concurrent.Semaphore. We use the latter,
+ // as the former is not supposed to be used directly (see b/63822989).
+ private final static class QueuedWait extends Operation {
+ private final static int SLEEP_TIME = 100;
+
+ private final Semaphore semaphore;
+
+ public QueuedWait(Semaphore semaphore) {
+ this.semaphore = semaphore;
+ }
+
+ @Override
+ public boolean perform() {
+ boolean permitAcquired = false;
+ try {
+ semaphore.acquire();
+ permitAcquired = true;
+ Thread.sleep(SLEEP_TIME);
+ } catch (OutOfMemoryError ignored) {
+ // The call to semaphore.acquire() above may trigger an OOME,
+ // despite the care taken doing some warm-up by forcing
+ // ahead-of-time initialization of classes used by the Semaphore
+ // class (see forceTransitiveClassInitialization below).
+ // For instance, one of the code paths executes
+ // AbstractQueuedSynchronizer.addWaiter, which allocates an
+ // AbstractQueuedSynchronizer$Node (see b/67730573).
+ // In that case, just ignore the OOME and continue.
+ } catch (InterruptedException ignored) {
+ } finally {
+ if (permitAcquired) {
+ semaphore.release();
+ }
+ }
+ return true;
+ }
+ }
+
+ private final static Map<Operation, Double> createDefaultFrequencyMap(Object lock,
+ Semaphore semaphore) {
+ Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>();
+ frequencyMap.put(new OOM(), 0.005); // 1/200
+ frequencyMap.put(new SigQuit(), 0.095); // 19/200
+ frequencyMap.put(new Alloc(), 0.2); // 40/200
+ frequencyMap.put(new LargeAlloc(), 0.05); // 10/200
+ frequencyMap.put(new NonMovingAlloc(), 0.025); // 5/200
+ frequencyMap.put(new StackTrace(), 0.1); // 20/200
+ frequencyMap.put(new Exit(), 0.225); // 45/200
+ frequencyMap.put(new Sleep(), 0.125); // 25/200
+ frequencyMap.put(new TimedWait(lock), 0.05); // 10/200
+ frequencyMap.put(new Wait(lock), 0.075); // 15/200
+ frequencyMap.put(new QueuedWait(semaphore), 0.05); // 10/200
+
+ return frequencyMap;
+ }
+
+ private final static Map<Operation, Double> createAllocFrequencyMap() {
+ Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>();
+ frequencyMap.put(new Sleep(), 0.2); // 40/200
+ frequencyMap.put(new Alloc(), 0.575); // 115/200
+ frequencyMap.put(new LargeAlloc(), 0.15); // 30/200
+ frequencyMap.put(new NonMovingAlloc(), 0.075); // 15/200
+
+ return frequencyMap;
+ }
+
+ private final static Map<Operation, Double> createLockFrequencyMap(Object lock) {
+ Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>();
+ frequencyMap.put(new Sleep(), 0.2); // 40/200
+ frequencyMap.put(new TimedWait(lock), 0.2); // 40/200
+ frequencyMap.put(new Wait(lock), 0.2); // 40/200
+ frequencyMap.put(new SyncAndWork(lock), 0.4); // 80/200
+
+ return frequencyMap;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
+ parseAndRun(args);
+ }
+
+ private static Map<Operation, Double> updateFrequencyMap(Map<Operation, Double> in,
+ Object lock, Semaphore semaphore, String arg) {
+ String split[] = arg.split(":");
+ if (split.length != 2) {
+ throw new IllegalArgumentException("Can't split argument " + arg);
+ }
+ double d;
+ try {
+ d = Double.parseDouble(split[1]);
+ } catch (Exception e) {
+ throw new IllegalArgumentException(e);
+ }
+ if (d < 0) {
+ throw new IllegalArgumentException(arg + ": value must be >= 0.");
+ }
+ Operation op = null;
+ if (split[0].equals("-oom")) {
+ op = new OOM();
+ } else if (split[0].equals("-sigquit")) {
+ op = new SigQuit();
+ } else if (split[0].equals("-alloc")) {
+ op = new Alloc();
+ } else if (split[0].equals("-largealloc")) {
+ op = new LargeAlloc();
+ } else if (split[0].equals("-stacktrace")) {
+ op = new StackTrace();
+ } else if (split[0].equals("-exit")) {
+ op = new Exit();
+ } else if (split[0].equals("-sleep")) {
+ op = new Sleep();
+ } else if (split[0].equals("-wait")) {
+ op = new Wait(lock);
+ } else if (split[0].equals("-timedwait")) {
+ op = new TimedWait(lock);
+ } else if (split[0].equals("-syncandwork")) {
+ op = new SyncAndWork(lock);
+ } else if (split[0].equals("-queuedwait")) {
+ op = new QueuedWait(semaphore);
+ } else {
+ throw new IllegalArgumentException("Unknown arg " + arg);
+ }
+
+ if (in == null) {
+ in = new HashMap<Operation, Double>();
+ }
+ in.put(op, d);
+
+ return in;
+ }
+
+ private static void normalize(Map<Operation, Double> map) {
+ double sum = 0;
+ for (Double d : map.values()) {
+ sum += d;
+ }
+ if (sum == 0) {
+ throw new RuntimeException("No elements!");
+ }
+ if (sum != 1.0) {
+ // Avoid ConcurrentModificationException.
+ Set<Operation> tmp = new HashSet<>(map.keySet());
+ for (Operation op : tmp) {
+ map.put(op, map.get(op) / sum);
+ }
+ }
+ }
+
+ public static void parseAndRun(String[] args) throws Exception {
+ int numberOfThreads = -1;
+ int numberOfDaemons = -1;
+ int totalOperations = -1;
+ int operationsPerThread = -1;
+ int permits = -1;
+ Object lock = new Object();
+ Map<Operation, Double> frequencyMap = null;
+ boolean dumpMap = false;
+
+ if (args != null) {
+ // args[0] is libarttest
+ for (int i = 1; i < args.length; i++) {
+ if (args[i].equals("-n")) {
+ i++;
+ numberOfThreads = Integer.parseInt(args[i]);
+ } else if (args[i].equals("-d")) {
+ i++;
+ numberOfDaemons = Integer.parseInt(args[i]);
+ } else if (args[i].equals("-o")) {
+ i++;
+ totalOperations = Integer.parseInt(args[i]);
+ } else if (args[i].equals("-t")) {
+ i++;
+ operationsPerThread = Integer.parseInt(args[i]);
+ } else if (args[i].equals("-p")) {
+ i++;
+ permits = Integer.parseInt(args[i]);
+ } else if (args[i].equals("--locks-only")) {
+ frequencyMap = createLockFrequencyMap(lock);
+ } else if (args[i].equals("--allocs-only")) {
+ frequencyMap = createAllocFrequencyMap();
+ } else if (args[i].equals("--dumpmap")) {
+ dumpMap = true;
+ } else {
+ // Processing an argument of the form "-<operation>:X"
+ // (where X is a double value).
+ Semaphore semaphore = getSemaphore(permits);
+ frequencyMap = updateFrequencyMap(frequencyMap, lock, semaphore, args[i]);
+ }
+ }
+ }
+
+ if (totalOperations != -1 && operationsPerThread != -1) {
+ throw new IllegalArgumentException(
+ "Specified both totalOperations and operationsPerThread");
+ }
+
+ if (numberOfThreads == -1) {
+ numberOfThreads = 5;
+ }
+
+ if (numberOfDaemons == -1) {
+ numberOfDaemons = 3;
+ }
+
+ if (totalOperations == -1) {
+ totalOperations = 1000;
+ }
+
+ if (operationsPerThread == -1) {
+ operationsPerThread = totalOperations/numberOfThreads;
+ }
+
+ if (frequencyMap == null) {
+ Semaphore semaphore = getSemaphore(permits);
+ frequencyMap = createDefaultFrequencyMap(lock, semaphore);
+ }
+ normalize(frequencyMap);
+
+ if (dumpMap) {
+ System.out.println(frequencyMap);
+ }
+
+ try {
+ runTest(numberOfThreads, numberOfDaemons, operationsPerThread, lock, frequencyMap);
+ } catch (Throwable t) {
+ // In this case, the output should not contain all the required
+ // "Finishing worker" lines.
+ Main.printThrowable(t);
+ }
+ }
+
+ private static Semaphore getSemaphore(int permits) {
+ if (permits == -1) {
+ // Default number of permits.
+ permits = 3;
+ }
+
+ Semaphore semaphore = new Semaphore(permits, /* fair */ true);
+ forceTransitiveClassInitialization(semaphore, permits);
+ return semaphore;
+ }
+
+ // Force ahead-of-time initialization of classes used by Semaphore
+ // code. Try to exercise all code paths likely to be taken during
+ // the actual test later (including having a thread blocking on
+ // the semaphore trying to acquire a permit), so that we increase
+ // the chances to initialize all classes indirectly used by
+ // QueuedWait (e.g. AbstractQueuedSynchronizer$Node).
+ private static void forceTransitiveClassInitialization(Semaphore semaphore, final int permits) {
+ // Ensure `semaphore` has the expected number of permits
+ // before we start.
+ assert semaphore.availablePermits() == permits;
+
+ // Let the main (current) thread acquire all permits from
+ // `semaphore`. Then create an auxiliary thread acquiring a
+ // permit from `semaphore`, blocking because none is
+ // available. Have the main thread release one permit, thus
+ // unblocking the second thread.
+
+ // Auxiliary thread.
+ Thread auxThread = new Thread("Aux") {
+ public void run() {
+ try {
+ // Try to acquire one permit, and block until
+ // that permit is released by the main thread.
+ semaphore.acquire();
+ // When unblocked, release the acquired permit
+ // immediately.
+ semaphore.release();
+ } catch (InterruptedException ignored) {
+ throw new RuntimeException("Test set up failed in auxiliary thread");
+ }
+ }
+ };
+
+ // Main thread.
+ try {
+ // Acquire all permits.
+ semaphore.acquire(permits);
+ // Start the auxiliary thread and have it try to acquire a
+ // permit.
+ auxThread.start();
+ // Synchronization: Wait until the auxiliary thread is
+ // blocked trying to acquire a permit from `semaphore`.
+ while (!semaphore.hasQueuedThreads()) {
+ Thread.sleep(100);
+ }
+ // Release one permit, thus unblocking `auxThread` and let
+ // it acquire a permit.
+ semaphore.release();
+ // Synchronization: Wait for the auxiliary thread to die.
+ auxThread.join();
+ // Release remaining permits.
+ semaphore.release(permits - 1);
+
+ // Verify that all permits have been released.
+ assert semaphore.availablePermits() == permits;
+ } catch (InterruptedException ignored) {
+ throw new RuntimeException("Test set up failed in main thread");
+ }
+ }
+
+ public static void runTest(final int numberOfThreads, final int numberOfDaemons,
+ final int operationsPerThread, final Object lock,
+ Map<Operation, Double> frequencyMap) throws Exception {
+ final Thread mainThread = Thread.currentThread();
+ final Barrier startBarrier = new Barrier(numberOfThreads + numberOfDaemons + 1);
+
+ // Each normal thread is going to do operationsPerThread
+ // operations. Each daemon thread will loop over all
+ // the operations and will not stop.
+ // The distribution of operations is determined by
+ // the frequencyMap values. We fill out an Operation[]
+ // for each thread with the operations it is to perform. The
+ // Operation[] is shuffled so that there is more random
+ // interactions between the threads.
+
+ // Fill in the Operation[] array for each thread by laying
+ // down references to operation according to their desired
+ // frequency.
+ // The first numberOfThreads elements are normal threads, the last
+ // numberOfDaemons elements are daemon threads.
+ final Main[] threadStresses = new Main[numberOfThreads + numberOfDaemons];
+ for (int t = 0; t < threadStresses.length; t++) {
+ Operation[] operations = new Operation[operationsPerThread];
+ int o = 0;
+ LOOP:
+ while (true) {
+ for (Operation op : frequencyMap.keySet()) {
+ int freq = (int)(frequencyMap.get(op) * operationsPerThread);
+ for (int f = 0; f < freq; f++) {
+ if (o == operations.length) {
+ break LOOP;
+ }
+ operations[o] = op;
+ o++;
+ }
+ }
+ }
+ // Randomize the operation order
+ Collections.shuffle(Arrays.asList(operations));
+ threadStresses[t] = (t < numberOfThreads)
+ ? new Main(lock, t, operations)
+ : new Daemon(lock, t, operations, mainThread, startBarrier);
+ }
+
+ // Enable to dump operation counts per thread to make sure its
+ // sane compared to frequencyMap.
+ if (DEBUG) {
+ for (int t = 0; t < threadStresses.length; t++) {
+ Operation[] operations = threadStresses[t].operations;
+ Map<Operation, Integer> distribution = new HashMap<Operation, Integer>();
+ for (Operation operation : operations) {
+ Integer ops = distribution.get(operation);
+ if (ops == null) {
+ ops = 1;
+ } else {
+ ops++;
+ }
+ distribution.put(operation, ops);
+ }
+ System.out.println("Distribution for " + t);
+ for (Operation op : frequencyMap.keySet()) {
+ System.out.println(op + " = " + distribution.get(op));
+ }
+ }
+ }
+
+ // Create the runners for each thread. The runner Thread
+ // ensures that thread that exit due to operation Exit will be
+ // restarted until they reach their desired
+ // operationsPerThread.
+ Thread[] runners = new Thread[numberOfThreads];
+ for (int r = 0; r < runners.length; r++) {
+ final Main ts = threadStresses[r];
+ runners[r] = new Thread("Runner thread " + r) {
+ final Main threadStress = ts;
+ public void run() {
+ try {
+ int id = threadStress.id;
+ // No memory hungry task are running yet, so println() should succeed.
+ System.out.println("Starting worker for " + id);
+ // Wait until all runners and daemons reach the starting point.
+ startBarrier.await();
+ // Run the stress tasks.
+ while (threadStress.nextOperation < operationsPerThread) {
+ try {
+ Thread thread = new Thread(ts, "Worker thread " + id);
+ thread.start();
+ thread.join();
+
+ if (DEBUG) {
+ System.out.println(
+ "Thread exited for " + id + " with " +
+ (operationsPerThread - threadStress.nextOperation) +
+ " operations remaining.");
+ }
+ } catch (OutOfMemoryError e) {
+ // Ignore OOME since we need to print "Finishing worker"
+ // for the test to pass. This OOM can come from creating
+ // the Thread or from the DEBUG output.
+ // Note that the Thread creation may fail repeatedly,
+ // preventing the runner from making any progress,
+ // especially if the number of daemons is too high.
+ }
+ }
+ // Print "Finishing worker" through JNI to avoid OOME.
+ Main.printString(Main.finishingWorkerMessage);
+ } catch (Throwable t) {
+ Main.printThrowable(t);
+ // Interrupt the main thread, so that it can orderly shut down
+ // instead of waiting indefinitely for some Barrier.
+ mainThread.interrupt();
+ }
+ }
+ };
+ }
+
+ // The notifier thread is a daemon just loops forever to wake
+ // up threads in operation Wait.
+ if (lock != null) {
+ Thread notifier = new Thread("Notifier") {
+ public void run() {
+ while (true) {
+ synchronized (lock) {
+ lock.notifyAll();
+ }
+ }
+ }
+ };
+ notifier.setDaemon(true);
+ notifier.start();
+ }
+
+ // Create and start the daemon threads.
+ for (int r = 0; r < numberOfDaemons; r++) {
+ Main daemon = threadStresses[numberOfThreads + r];
+ Thread t = new Thread(daemon, "Daemon thread " + daemon.id);
+ t.setDaemon(true);
+ t.start();
+ }
+
+ for (int r = 0; r < runners.length; r++) {
+ runners[r].start();
+ }
+ // Wait for all threads to reach the starting point.
+ startBarrier.await();
+ // Wait for runners to finish.
+ for (int r = 0; r < runners.length; r++) {
+ runners[r].join();
+ }
+ }
+
+ protected final Operation[] operations;
+ private final Object lock;
+ protected final int id;
+
+ private int nextOperation;
+
+ private Main(Object lock, int id, Operation[] operations) {
+ this.lock = lock;
+ this.id = id;
+ this.operations = operations;
+ }
+
+ public void run() {
+ try {
+ if (DEBUG) {
+ System.out.println("Starting ThreadStress " + id);
+ }
+ while (nextOperation < operations.length) {
+ Operation operation = operations[nextOperation];
+ if (DEBUG) {
+ System.out.println("ThreadStress " + id
+ + " operation " + nextOperation
+ + " is " + operation);
+ }
+ nextOperation++;
+ if (!operation.perform()) {
+ return;
+ }
+ }
+ } finally {
+ if (DEBUG) {
+ System.out.println("Finishing ThreadStress for " + id);
+ }
+ }
+ }
+
+ private static class Daemon extends Main {
+ private Daemon(Object lock,
+ int id,
+ Operation[] operations,
+ Thread mainThread,
+ Barrier startBarrier) {
+ super(lock, id, operations);
+ this.mainThread = mainThread;
+ this.startBarrier = startBarrier;
+ }
+
+ public void run() {
+ try {
+ if (DEBUG) {
+ System.out.println("Starting ThreadStress Daemon " + id);
+ }
+ startBarrier.await();
+ try {
+ int i = 0;
+ while (true) {
+ Operation operation = operations[i];
+ if (DEBUG) {
+ System.out.println("ThreadStress Daemon " + id
+ + " operation " + i
+ + " is " + operation);
+ }
+ // Ignore the result of the performed operation, making
+ // Exit.perform() essentially a no-op for daemon threads.
+ operation.perform();
+ i = (i + 1) % operations.length;
+ }
+ } catch (OutOfMemoryError e) {
+ // Catch OutOfMemoryErrors since these can cause the test to fail it they print
+ // the stack trace after "Finishing worker". Note that operations should catch
+ // their own OOME, this guards only agains OOME in the DEBUG output.
+ }
+ if (DEBUG) {
+ System.out.println("Finishing ThreadStress Daemon for " + id);
+ }
+ } catch (Throwable t) {
+ Main.printThrowable(t);
+ // Interrupt the main thread, so that it can orderly shut down
+ // instead of waiting indefinitely for some Barrier.
+ mainThread.interrupt();
+ }
+ }
+
+ final Thread mainThread;
+ final Barrier startBarrier;
+ }
+
+ // Note: java.util.concurrent.CyclicBarrier.await() allocates memory and may throw OOM.
+ // That is highly undesirable in this test, so we use our own simple barrier class.
+ // The only memory allocation that can happen here is the lock inflation which uses
+ // a native allocation. As such, it should succeed even if the Java heap is full.
+ // If the native allocation surprisingly fails, the program shall abort().
+ private static class Barrier {
+ public Barrier(int initialCount) {
+ count = initialCount;
+ }
+
+ public synchronized void await() throws InterruptedException {
+ --count;
+ if (count != 0) {
+ do {
+ wait();
+ } while (count != 0); // Check for spurious wakeup.
+ } else {
+ notifyAll();
+ }
+ }
+
+ private int count;
+ }
+
+ // Printing a String/Throwable through JNI requires only native memory and space
+ // in the local reference table, so it should succeed even if the Java heap is full.
+ private static native void printString(String s);
+ private static native void printThrowable(Throwable t);
+
+ static final String finishingWorkerMessage;
+ static final String errnoExceptionName;
+ static {
+ // We pre-allocate the strings in class initializer to avoid const-string
+ // instructions in code using these strings later as they may throw OOME.
+ finishingWorkerMessage = "Finishing worker\n";
+ errnoExceptionName = "ErrnoException";
+ }
+}