Merge "Add an operation using a semaphore to test 004-ThreadStress."
am: 46a62897ce

Change-Id: I6e2080d5da3200b2e5025d04e472d66c894823ea
diff --git a/test/004-ThreadStress/src/Main.java b/test/004-ThreadStress/src/Main.java
index 95d1649..35f394c 100644
--- a/test/004-ThreadStress/src/Main.java
+++ b/test/004-ThreadStress/src/Main.java
@@ -23,6 +23,7 @@
 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
@@ -35,14 +36,19 @@
 //    -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
 //    -oom:X .......... frequency of OOM (double)
-//    -alloc:X ........ frequency of Alloc
-//    -stacktrace:X ... frequency of StackTrace
-//    -exit:X ......... frequency of Exit
-//    -sleep:X ........ frequency of Sleep
-//    -wait:X ......... frequency of Wait
-//    -timedwait:X .... frequency of TimedWait
+//    -sigquit:X ...... frequency of SigQuit (double)
+//    -alloc:X ........ frequency of Alloc (double)
+//    -largealloc:X ... frequency of LargeAlloc (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 {
 
@@ -51,7 +57,7 @@
     private static abstract class Operation {
         /**
          * Perform the action represented by this operation. Returns true if the thread should
-         * continue.
+         * continue when executed by a runner (non-daemon) thread.
          */
         public abstract boolean perform();
     }
@@ -240,27 +246,60 @@
         }
     }
 
-    private final static Map<Operation, Double> createDefaultFrequencyMap(Object lock) {
+    // 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 (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.25);            // 50/200
-        frequencyMap.put(new LargeAlloc(), 0.05);       // 10/200
-        frequencyMap.put(new StackTrace(), 0.1);        // 20/200
-        frequencyMap.put(new Exit(), 0.25);             // 50/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 OOM(), 0.005);                   //  1/200
+        frequencyMap.put(new SigQuit(), 0.095);               // 19/200
+        frequencyMap.put(new Alloc(), 0.225);                 // 45/200
+        frequencyMap.put(new LargeAlloc(), 0.05);             // 10/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> createLockFrequencyMap(Object lock) {
       Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>();
-      frequencyMap.put(new Sleep(), 0.2);
-      frequencyMap.put(new TimedWait(lock), 0.2);
-      frequencyMap.put(new Wait(lock), 0.2);
-      frequencyMap.put(new SyncAndWork(lock), 0.4);
+      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;
     }
@@ -271,7 +310,7 @@
     }
 
     private static Map<Operation, Double> updateFrequencyMap(Map<Operation, Double> in,
-            Object lock, String arg) {
+            Object lock, Semaphore semaphore, String arg) {
         String split[] = arg.split(":");
         if (split.length != 2) {
             throw new IllegalArgumentException("Can't split argument " + arg);
@@ -304,6 +343,10 @@
             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);
         }
@@ -338,6 +381,7 @@
         int numberOfDaemons = -1;
         int totalOperations = -1;
         int operationsPerThread = -1;
+        int permits = -1;
         Object lock = new Object();
         Map<Operation, Double> frequencyMap = null;
         boolean dumpMap = false;
@@ -357,13 +401,17 @@
                 } 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")) {
                     lock = new Object();
                     frequencyMap = createLockFrequencyMap(lock);
                 } else if (args[i].equals("--dumpmap")) {
                     dumpMap = true;
                 } else {
-                    frequencyMap = updateFrequencyMap(frequencyMap, lock, args[i]);
+                    Semaphore semaphore = getSemaphore(permits);
+                    frequencyMap = updateFrequencyMap(frequencyMap, lock, semaphore, args[i]);
                 }
             }
         }
@@ -390,7 +438,8 @@
         }
 
         if (frequencyMap == null) {
-            frequencyMap = createDefaultFrequencyMap(lock);
+            Semaphore semaphore = getSemaphore(permits);
+            frequencyMap = createDefaultFrequencyMap(lock, semaphore);
         }
         normalize(frequencyMap);
 
@@ -407,6 +456,15 @@
         }
     }
 
+    private static Semaphore getSemaphore(int permits) {
+        if (permits == -1) {
+            // Default number of permits.
+            permits = 3;
+        }
+
+        return new Semaphore(permits, /* fair */ true);
+    }
+
     public static void runTest(final int numberOfThreads, final int numberOfDaemons,
                                final int operationsPerThread, final Object lock,
                                Map<Operation, Double> frequencyMap) throws Exception {
@@ -417,7 +475,7 @@
         // operations. Each daemon thread will loop over all
         // the operations and will not stop.
         // The distribution of operations is determined by
-        // the Operation.frequency values. We fill out an Operation[]
+        // 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.
@@ -452,7 +510,7 @@
         }
 
         // Enable to dump operation counts per thread to make sure its
-        // sane compared to Operation.frequency
+        // sane compared to frequencyMap.
         if (DEBUG) {
             for (int t = 0; t < threadStresses.length; t++) {
                 Operation[] operations = threadStresses[t].operations;
@@ -474,7 +532,7 @@
         }
 
         // Create the runners for each thread. The runner Thread
-        // ensures that thread that exit due to Operation.EXIT will be
+        // ensures that thread that exit due to operation Exit will be
         // restarted until they reach their desired
         // operationsPerThread.
         Thread[] runners = new Thread[numberOfThreads];
@@ -524,7 +582,7 @@
         }
 
         // The notifier thread is a daemon just loops forever to wake
-        // up threads in Operation.WAIT
+        // up threads in operation Wait.
         if (lock != null) {
             Thread notifier = new Thread("Notifier") {
                 public void run() {
@@ -620,6 +678,8 @@
                                                + " 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;
                     }