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
17package tests.api.org.apache.harmony.kernel.dalvik;
18
19import java.lang.reflect.Field;
20import java.util.concurrent.CyclicBarrier;
21import java.util.concurrent.TimeUnit;
22import junit.framework.Assert;
23import junit.framework.TestCase;
24import sun.misc.Unsafe;
25
26/**
27 * Tests for the <code>park()</code> functionality of {@link Unsafe}.
28 */
29public 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