/* * Copyright (C) 2007 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. */ package tests.api.org.apache.harmony.kernel.dalvik; import java.lang.reflect.Field; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; import junit.framework.Assert; import junit.framework.TestCase; import sun.misc.Unsafe; /** * Tests for the <code>park()</code> functionality of {@link Unsafe}. */ public class ThreadsTest extends TestCase { private static Unsafe UNSAFE = null; static { /* * Set up {@link #UNSAFE}. This subverts the access check to * get the unique Unsafe instance. We can do this because * there's no security manager installed when running the * test. */ try { Field field = Unsafe.class.getDeclaredField("THE_ONE"); field.setAccessible(true); UNSAFE = (Unsafe) field.get(null); } catch (NoSuchFieldException ex) { throw new RuntimeException(ex); } catch (IllegalAccessException ex) { throw new RuntimeException(ex); } } /** Test the case where the park times out. */ public void test_parkFor_1() throws Exception { CyclicBarrier barrier = new CyclicBarrier(2); Parker parker = new Parker(barrier, false, 500); Thread parkerThread = new Thread(parker); Thread waiterThread = new Thread(new WaitAndUnpark(barrier, 1000, parkerThread)); parkerThread.start(); waiterThread.start(); parker.assertDurationIsInRange(500); waiterThread.join(); parkerThread.join(); } /** Test the case where the unpark happens before the timeout. */ public void test_parkFor_2() throws Exception { CyclicBarrier barrier = new CyclicBarrier(2); Parker parker = new Parker(barrier, false, 1000); Thread parkerThread = new Thread(parker); Thread waiterThread = new Thread(new WaitAndUnpark(barrier, 300, parkerThread)); parkerThread.start(); waiterThread.start(); parker.assertDurationIsInRange(300); waiterThread.join(); parkerThread.join(); } /** Test the case where the thread is preemptively unparked. */ public void test_parkFor_3() throws Exception { CyclicBarrier barrier = new CyclicBarrier(1); Parker parker = new Parker(barrier, false, 1000); Thread parkerThread = new Thread(parker); UNSAFE.unpark(parkerThread); parkerThread.start(); parker.assertDurationIsInRange(0); parkerThread.join(); } /** Test the case where the park times out. */ public void test_parkUntil_1() throws Exception { CyclicBarrier barrier = new CyclicBarrier(2); Parker parker = new Parker(barrier, true, 500); Thread parkerThread = new Thread(parker); Thread waiterThread = new Thread(new WaitAndUnpark(barrier, 1000, parkerThread)); parkerThread.start(); waiterThread.start(); parker.assertDurationIsInRange(500); waiterThread.join(); parkerThread.join(); } /** Test the case where the unpark happens before the timeout. */ public void test_parkUntil_2() throws Exception { CyclicBarrier barrier = new CyclicBarrier(2); Parker parker = new Parker(barrier, true, 1000); Thread parkerThread = new Thread(parker); Thread waiterThread = new Thread(new WaitAndUnpark(barrier, 300, parkerThread)); parkerThread.start(); waiterThread.start(); parker.assertDurationIsInRange(300); waiterThread.join(); parkerThread.join(); } /** Test the case where the thread is preemptively unparked. */ public void test_parkUntil_3() throws Exception { CyclicBarrier barrier = new CyclicBarrier(1); Parker parker = new Parker(barrier, true, 1000); Thread parkerThread = new Thread(parker); UNSAFE.unpark(parkerThread); parkerThread.start(); parker.assertDurationIsInRange(0); parkerThread.join(); } // TODO: Add more tests. /** * Helper <code>Runnable</code> for tests, which parks for or until * the indicated value, noting the duration of time actually parked. */ private static class Parker implements Runnable { private final CyclicBarrier barrier; /** whether {@link #amount} is milliseconds to wait in an * absolute fashion (<code>true</code>) or nanoseconds to wait * in a relative fashion (<code>false</code>) */ private final boolean absolute; /** amount to wait (see above) */ private final long amount; /** whether the run has completed */ private boolean completed; /** recorded start time */ private long startMillis; /** recorded end time */ private long endMillis; /** * Construct an instance. * * @param absolute whether to use an absolute time or not; in * either case, this constructor takes a duration to park for * @param parkMillis the number of milliseconds to be parked */ public Parker(CyclicBarrier barrier, boolean absolute, long parkMillis) { this.barrier = barrier; this.absolute = absolute; // Multiply by 1000000 because parkFor() takes nanoseconds. this.amount = absolute ? parkMillis : parkMillis * 1000000; } public void run() { try { barrier.await(60, TimeUnit.SECONDS); } catch (Exception e) { throw new AssertionError(e); } boolean absolute = this.absolute; long amount = this.amount; long startNanos = System.nanoTime(); long start = System.currentTimeMillis(); if (absolute) { UNSAFE.park(true, start + amount); } else { UNSAFE.park(false, amount); } long endNanos = System.nanoTime(); synchronized (this) { startMillis = startNanos / 1000000; endMillis = endNanos / 1000000; completed = true; notifyAll(); } } /** * Wait for the test to complete and return the duration. * * @param maxWaitMillis the maximum amount of time to * wait for the test to complete * @return the duration in milliseconds */ public long getDurationMillis(long maxWaitMillis) { synchronized (this) { if (! completed) { try { wait(maxWaitMillis); } catch (InterruptedException ignored) { } if (! completed) { Assert.fail("parker hung for more than " + maxWaitMillis + " ms"); } } return endMillis - startMillis; } } /** * Asserts that the actual duration is within 10% of the * given expected time. * * @param expectedMillis the expected duration, in milliseconds */ public void assertDurationIsInRange(long expectedMillis) { /* * Allow a bit more slop for the maximum on "expected * instantaneous" results. */ long minimum = (long) ((double) expectedMillis * 0.80); long maximum = Math.max((long) ((double) expectedMillis * 1.20), 10); long waitMillis = Math.max(expectedMillis * 10, 30); long duration = getDurationMillis(waitMillis); if (duration < minimum) { Assert.fail("expected duration: " + expectedMillis + " minimum duration: " + minimum + " actual duration too short: " + duration); } else if (duration > maximum) { Assert.fail("expected duration: " + expectedMillis + " maximum duration: " + maximum + " actual duration too long: " + duration); } } } /** * Helper <code>Runnable</code> for tests, which waits for the * specified amount of time and then unparks an indicated thread. */ private static class WaitAndUnpark implements Runnable { private final CyclicBarrier barrier; private final long waitMillis; private final Thread thread; public WaitAndUnpark(CyclicBarrier barrier, long waitMillis, Thread thread) { this.barrier = barrier; this.waitMillis = waitMillis; this.thread = thread; } public void run() { try { barrier.await(60, TimeUnit.SECONDS); } catch (Exception e) { throw new AssertionError(e); } try { Thread.sleep(waitMillis); } catch (InterruptedException ex) { throw new RuntimeException("shouldn't happen", ex); } UNSAFE.unpark(thread); } } }