878 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			878 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			Java
		
	
	
	
| /*
 | |
|  * 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;
 | |
| import java.util.concurrent.locks.LockSupport;
 | |
| 
 | |
| // 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)
 | |
| //    -timedpark:X ...... frequency of TimedPark (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 TimedPark extends Operation {
 | |
|         private final static int SLEEP_TIME = 100;
 | |
| 
 | |
|         public TimedPark() {}
 | |
| 
 | |
|         @Override
 | |
|         public boolean perform() {
 | |
|             LockSupport.parkNanos(this, 100*1000000);
 | |
|             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.075);                 //  15/200
 | |
|         frequencyMap.put(new TimedPark(), 0.05);              //  10/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.1);             //  20/200
 | |
|       frequencyMap.put(new Wait(lock), 0.2);                  //  40/200
 | |
|       frequencyMap.put(new SyncAndWork(lock), 0.4);           //  80/200
 | |
|       frequencyMap.put(new TimedPark(), 0.1);                 //  20/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("-nonmovingalloc")) {
 | |
|             op = new NonMovingAlloc();
 | |
|         } 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("-timedpark")) {
 | |
|             op = new TimedPark();
 | |
|         } 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 see that it is
 | |
|         // commensurate with the 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 operations Wait and Park.
 | |
|         if (lock != null) {
 | |
|             Thread notifier = new Thread("Notifier") {
 | |
|                 public void run() {
 | |
|                     while (true) {
 | |
|                         synchronized (lock) {
 | |
|                             lock.notifyAll();
 | |
|                         }
 | |
|                         for (Thread runner : runners) {
 | |
|                           if (runner != null) {
 | |
|                             LockSupport.unpark(runner);
 | |
|                           }
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             };
 | |
|             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";
 | |
|     }
 | |
| }
 |