Home | History | Annotate | Download | only in dalvik
      1 /*
      2  * Copyright (C) 2007 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 package tests.api.org.apache.harmony.kernel.dalvik;
     18 
     19 import java.lang.reflect.Field;
     20 import java.util.concurrent.CyclicBarrier;
     21 import java.util.concurrent.TimeUnit;
     22 import junit.framework.Assert;
     23 import junit.framework.TestCase;
     24 import sun.misc.Unsafe;
     25 
     26 /**
     27  * Tests for the <code>park()</code> functionality of {@link Unsafe}.
     28  */
     29 public class ThreadsTest extends TestCase {
     30     private static Unsafe UNSAFE = null;
     31     static {
     32         /*
     33          * Set up {@link #UNSAFE}. This subverts the access check to
     34          * get the unique Unsafe instance. We can do this because
     35          * there's no security manager installed when running the
     36          * test.
     37          */
     38         try {
     39             Field field = Unsafe.class.getDeclaredField("THE_ONE");
     40             field.setAccessible(true);
     41 
     42             UNSAFE = (Unsafe) field.get(null);
     43         } catch (NoSuchFieldException ex) {
     44             throw new RuntimeException(ex);
     45         } catch (IllegalAccessException ex) {
     46             throw new RuntimeException(ex);
     47         }
     48     }
     49 
     50     /** Test the case where the park times out. */
     51     public void test_parkFor_1() throws Exception {
     52         CyclicBarrier barrier = new CyclicBarrier(2);
     53         Parker parker = new Parker(barrier, false, 500);
     54         Thread parkerThread = new Thread(parker);
     55         Thread waiterThread =
     56             new Thread(new WaitAndUnpark(barrier, 1000, parkerThread));
     57 
     58         parkerThread.start();
     59         waiterThread.start();
     60         parker.assertDurationIsInRange(500);
     61         waiterThread.join();
     62         parkerThread.join();
     63     }
     64 
     65     /** Test the case where the unpark happens before the timeout. */
     66     public void test_parkFor_2() throws Exception {
     67         CyclicBarrier barrier = new CyclicBarrier(2);
     68         Parker parker = new Parker(barrier, false, 1000);
     69         Thread parkerThread = new Thread(parker);
     70         Thread waiterThread =
     71             new Thread(new WaitAndUnpark(barrier, 300, parkerThread));
     72 
     73         parkerThread.start();
     74         waiterThread.start();
     75         parker.assertDurationIsInRange(300);
     76         waiterThread.join();
     77         parkerThread.join();
     78     }
     79 
     80     /** Test the case where the thread is preemptively unparked. */
     81     public void test_parkFor_3() throws Exception {
     82         CyclicBarrier barrier = new CyclicBarrier(1);
     83         Parker parker = new Parker(barrier, false, 1000);
     84         Thread parkerThread = new Thread(parker);
     85 
     86         UNSAFE.unpark(parkerThread);
     87         parkerThread.start();
     88         parker.assertDurationIsInRange(0);
     89         parkerThread.join();
     90     }
     91 
     92     /** Test the case where the park times out. */
     93     public void test_parkUntil_1() throws Exception {
     94         CyclicBarrier barrier = new CyclicBarrier(2);
     95         Parker parker = new Parker(barrier, true, 500);
     96         Thread parkerThread = new Thread(parker);
     97         Thread waiterThread =
     98             new Thread(new WaitAndUnpark(barrier, 1000, parkerThread));
     99 
    100         parkerThread.start();
    101         waiterThread.start();
    102         parker.assertDurationIsInRange(500);
    103         waiterThread.join();
    104         parkerThread.join();
    105     }
    106 
    107     /** Test the case where the unpark happens before the timeout. */
    108     public void test_parkUntil_2() throws Exception {
    109         CyclicBarrier barrier = new CyclicBarrier(2);
    110         Parker parker = new Parker(barrier, true, 1000);
    111         Thread parkerThread = new Thread(parker);
    112         Thread waiterThread =
    113             new Thread(new WaitAndUnpark(barrier, 300, parkerThread));
    114 
    115         parkerThread.start();
    116         waiterThread.start();
    117         parker.assertDurationIsInRange(300);
    118         waiterThread.join();
    119         parkerThread.join();
    120     }
    121 
    122     /** Test the case where the thread is preemptively unparked. */
    123     public void test_parkUntil_3() throws Exception {
    124         CyclicBarrier barrier = new CyclicBarrier(1);
    125         Parker parker = new Parker(barrier, true, 1000);
    126         Thread parkerThread = new Thread(parker);
    127 
    128         UNSAFE.unpark(parkerThread);
    129         parkerThread.start();
    130         parker.assertDurationIsInRange(0);
    131         parkerThread.join();
    132     }
    133 
    134     // TODO: Add more tests.
    135 
    136     /**
    137      * Helper <code>Runnable</code> for tests, which parks for or until
    138      * the indicated value, noting the duration of time actually parked.
    139      */
    140     private static class Parker implements Runnable {
    141 
    142         private final CyclicBarrier barrier;
    143 
    144         /** whether {@link #amount} is milliseconds to wait in an
    145          * absolute fashion (<code>true</code>) or nanoseconds to wait
    146          * in a relative fashion (<code>false</code>) */
    147         private final boolean absolute;
    148 
    149         /** amount to wait (see above) */
    150         private final long amount;
    151 
    152         /** whether the run has completed */
    153         private boolean completed;
    154 
    155         /** recorded start time */
    156         private long startMillis;
    157 
    158         /** recorded end time */
    159         private long endMillis;
    160 
    161         /**
    162          * Construct an instance.
    163          *
    164          * @param absolute whether to use an absolute time or not; in
    165          * either case, this constructor takes a duration to park for
    166          * @param parkMillis the number of milliseconds to be parked
    167          */
    168         public Parker(CyclicBarrier barrier, boolean absolute, long parkMillis) {
    169             this.barrier = barrier;
    170             this.absolute = absolute;
    171 
    172             // Multiply by 1000000 because parkFor() takes nanoseconds.
    173             this.amount = absolute ? parkMillis : parkMillis * 1000000;
    174         }
    175 
    176         public void run() {
    177             try {
    178                 barrier.await(60, TimeUnit.SECONDS);
    179             } catch (Exception e) {
    180                 throw new AssertionError(e);
    181             }
    182             boolean absolute = this.absolute;
    183             long amount = this.amount;
    184             long startNanos = System.nanoTime();
    185             long start = System.currentTimeMillis();
    186 
    187             if (absolute) {
    188                 UNSAFE.park(true, start + amount);
    189             } else {
    190                 UNSAFE.park(false, amount);
    191             }
    192 
    193             long endNanos = System.nanoTime();
    194 
    195             synchronized (this) {
    196                 startMillis = startNanos / 1000000;
    197                 endMillis = endNanos / 1000000;
    198                 completed = true;
    199                 notifyAll();
    200             }
    201         }
    202 
    203         /**
    204          * Wait for the test to complete and return the duration.
    205          *
    206          * @param maxWaitMillis the maximum amount of time to
    207          * wait for the test to complete
    208          * @return the duration in milliseconds
    209          */
    210         public long getDurationMillis(long maxWaitMillis) {
    211             synchronized (this) {
    212                 if (! completed) {
    213                     try {
    214                         wait(maxWaitMillis);
    215                     } catch (InterruptedException ignored) {
    216                     }
    217                     if (! completed) {
    218                         Assert.fail("parker hung for more than " + maxWaitMillis + " ms");
    219                     }
    220                 }
    221 
    222                 return endMillis - startMillis;
    223             }
    224         }
    225 
    226         /**
    227          * Asserts that the actual duration is within 10% of the
    228          * given expected time.
    229          *
    230          * @param expectedMillis the expected duration, in milliseconds
    231          */
    232         public void assertDurationIsInRange(long expectedMillis) {
    233             /*
    234              * Allow a bit more slop for the maximum on "expected
    235              * instantaneous" results.
    236              */
    237             long minimum = (long) ((double) expectedMillis * 0.90);
    238             long maximum =
    239                 Math.max((long) ((double) expectedMillis * 1.10), 10);
    240             long waitMillis = Math.max(expectedMillis * 10, 10);
    241             long duration = getDurationMillis(waitMillis);
    242 
    243             if (duration < minimum) {
    244                 Assert.fail("expected duration: " + expectedMillis +
    245                             " minimum duration: " + minimum +
    246                             " actual duration too short: " + duration);
    247             } else if (duration > maximum) {
    248                 Assert.fail("expected duration: " + expectedMillis +
    249                             " maximum duration: " + maximum +
    250                             " actual duration too long: " + duration);
    251             }
    252         }
    253     }
    254 
    255     /**
    256      * Helper <code>Runnable</code> for tests, which waits for the
    257      * specified amount of time and then unparks an indicated thread.
    258      */
    259     private static class WaitAndUnpark implements Runnable {
    260         private final CyclicBarrier barrier;
    261         private final long waitMillis;
    262         private final Thread thread;
    263 
    264         public WaitAndUnpark(CyclicBarrier barrier, long waitMillis, Thread thread) {
    265             this.barrier = barrier;
    266             this.waitMillis = waitMillis;
    267             this.thread = thread;
    268         }
    269 
    270         public void run() {
    271             try {
    272                 barrier.await(60, TimeUnit.SECONDS);
    273             } catch (Exception e) {
    274                 throw new AssertionError(e);
    275             }
    276             try {
    277                 Thread.sleep(waitMillis);
    278             } catch (InterruptedException ex) {
    279                 throw new RuntimeException("shouldn't happen", ex);
    280             }
    281 
    282             UNSAFE.unpark(thread);
    283         }
    284     }
    285 }
    286