summaryrefslogtreecommitdiffstats
path: root/luni/src/test/java/tests/api/org/apache/harmony/kernel/dalvik/ThreadsTest.java
blob: 286d4ab46c8c247ef2896fcca22765270a742a82 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
/*
 * 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 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;
    private static RuntimeException INITIALIZEFAILED = 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) {
            INITIALIZEFAILED = new RuntimeException(ex);
        } catch (IllegalAccessException ex) {
            INITIALIZEFAILED = new RuntimeException(ex);
        }
    }

    /** Test the case where the park times out. */
    public void test_parkFor_1() {
        Parker parker = new Parker(false, 500);
        Thread parkerThread = new Thread(parker);
        Thread waiterThread =
            new Thread(new WaitAndUnpark(1000, parkerThread));

        parkerThread.start();
        waiterThread.start();
        parker.assertDurationIsInRange(500);
    }

    /** Test the case where the unpark happens before the timeout. */
    public void test_parkFor_2() {
        Parker parker = new Parker(false, 1000);
        Thread parkerThread = new Thread(parker);
        Thread waiterThread =
            new Thread(new WaitAndUnpark(300, parkerThread));

        parkerThread.start();
        waiterThread.start();
        parker.assertDurationIsInRange(300);
    }

    /** Test the case where the thread is preemptively unparked. */
    public void test_parkFor_3() {
        Parker parker = new Parker(false, 1000);
        Thread parkerThread = new Thread(parker);

        UNSAFE.unpark(parkerThread);
        parkerThread.start();
        parker.assertDurationIsInRange(0);
    }

    /** Test the case where the park times out. */
    public void test_parkUntil_1() {
        Parker parker = new Parker(true, 500);
        Thread parkerThread = new Thread(parker);
        Thread waiterThread =
            new Thread(new WaitAndUnpark(1000, parkerThread));

        parkerThread.start();
        waiterThread.start();
        parker.assertDurationIsInRange(500);
    }

    /** Test the case where the unpark happens before the timeout. */
    public void test_parkUntil_2() {
        Parker parker = new Parker(true, 1000);
        Thread parkerThread = new Thread(parker);
        Thread waiterThread =
            new Thread(new WaitAndUnpark(300, parkerThread));

        parkerThread.start();
        waiterThread.start();
        parker.assertDurationIsInRange(300);
    }

    /** Test the case where the thread is preemptively unparked. */
    public void test_parkUntil_3() {
        Parker parker = new Parker(true, 1000);
        Thread parkerThread = new Thread(parker);

        UNSAFE.unpark(parkerThread);
        parkerThread.start();
        parker.assertDurationIsInRange(0);
    }

    // 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 {
        /** 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(boolean absolute, long parkMillis) {
            this.absolute = absolute;

            // Multiply by 1000000 because parkFor() takes nanoseconds.
            this.amount = absolute ? parkMillis : parkMillis * 1000000;
        }

        public void run() {
            boolean absolute = this.absolute;
            long amount = this.amount;
            long start = System.currentTimeMillis();

            if (absolute) {
                UNSAFE.park(true, start + amount);
            } else {
                UNSAFE.park(false, amount);
            }

            long end = System.currentTimeMillis();

            synchronized (this) {
                startMillis = start;
                endMillis = end;
                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 ex) {
                        // Ignore it.
                    }
                    if (! completed) {
                        Assert.fail("parker hanging");
                    }
                }

                return endMillis - startMillis;
            }
        }

        /**
         * Asserts that the actual duration is within 5% 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.95);
            long maximum =
                Math.max((long) ((double) expectedMillis * 1.05), 10);
            long waitMillis = Math.max(expectedMillis * 10, 10);
            long duration = getDurationMillis(waitMillis);

            if (duration < minimum) {
                Assert.fail("expected duration: " + expectedMillis +
                        "; actual too short: " + duration);
            } else if (duration > maximum) {
                Assert.fail("expected duration: " + expectedMillis +
                        "; actual 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 long waitMillis;
        private final Thread thread;

        public WaitAndUnpark(long waitMillis, Thread thread) {
            this.waitMillis = waitMillis;
            this.thread = thread;
        }

        public void run() {
            try {
                Thread.sleep(waitMillis);
            } catch (InterruptedException ex) {
                throw new RuntimeException("shouldn't happen", ex);
            }

            UNSAFE.unpark(thread);
        }
    }

    @Override
    protected void setUp() throws Exception {
        if (INITIALIZEFAILED != null) {
            throw INITIALIZEFAILED;
        }
    }
}