SelfDestructiveThreadTest.java revision 2afabd060e15342c0ba622cb08f5be61a328e566
1/*
2 * Copyright (C) 2017 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 android.support.v4.provider;
18
19import static android.support.v4.provider.SelfDestructiveThread.ReplyCallback;
20
21import static org.junit.Assert.assertEquals;
22import static org.junit.Assert.assertFalse;
23import static org.junit.Assert.assertNotEquals;
24import static org.junit.Assert.assertNotNull;
25import static org.junit.Assert.fail;
26
27import android.os.Process;
28import android.support.annotation.GuardedBy;
29import android.support.test.InstrumentationRegistry;
30import android.support.test.filters.MediumTest;
31import android.support.test.runner.AndroidJUnit4;
32
33import org.junit.Test;
34import org.junit.runner.RunWith;
35
36import java.util.concurrent.Callable;
37import java.util.concurrent.TimeUnit;
38import java.util.concurrent.locks.Condition;
39import java.util.concurrent.locks.ReentrantLock;
40
41/**
42 * Tests for {@link SelfDestructiveThread}
43 */
44@RunWith(AndroidJUnit4.class)
45@MediumTest
46public class SelfDestructiveThreadTest {
47    private static final int DEFAULT_TIMEOUT = 1000;
48
49    private void waitUntilDestruction(SelfDestructiveThread thread, long timeoutMs) {
50        if (!thread.isRunning()) {
51            return;
52        }
53        final long deadlineNanoTime =
54                System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeoutMs);
55        final long timeSliceMs = 50;
56        do {
57            try {
58                Thread.sleep(timeSliceMs);
59            } catch (InterruptedException e) {
60                // ignore.
61            }
62            if (!thread.isRunning()) {
63                return;
64            }
65        } while (System.nanoTime() < deadlineNanoTime);
66        throw new RuntimeException("Timeout for waiting thread destruction.");
67    }
68
69    private void waitMillis(long ms) {
70        final long deadlineNanoTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(ms);
71        for (;;) {
72            final long now = System.nanoTime();
73            if (deadlineNanoTime < now) {
74                return;
75            }
76            try {
77                Thread.sleep(TimeUnit.NANOSECONDS.toMillis(deadlineNanoTime - now));
78            } catch (InterruptedException e) {
79                // ignore.
80            }
81        }
82    }
83
84    @Test
85    public void testDestruction() throws InterruptedException {
86        final int destructAfterLastActivityInMs = 300;
87        final SelfDestructiveThread thread = new SelfDestructiveThread(
88                "test", Process.THREAD_PRIORITY_BACKGROUND, destructAfterLastActivityInMs);
89        thread.postAndWait(new Callable<Object>() {
90            @Override
91            public Object call() throws Exception {
92                return null;
93            }
94        }, DEFAULT_TIMEOUT);
95        waitUntilDestruction(thread, DEFAULT_TIMEOUT);
96        assertFalse(thread.isRunning());
97    }
98
99    @Test
100    public void testReconstruction() throws InterruptedException {
101        final int destructAfterLastActivityInMs = 300;
102        final SelfDestructiveThread thread = new SelfDestructiveThread(
103                "test", Process.THREAD_PRIORITY_BACKGROUND, destructAfterLastActivityInMs);
104        Integer generation = thread.postAndWait(new Callable<Integer>() {
105            @Override
106            public Integer call() throws Exception {
107                return thread.getGeneration();
108            }
109        }, DEFAULT_TIMEOUT);
110        assertNotNull(generation);
111        waitUntilDestruction(thread, DEFAULT_TIMEOUT);
112        Integer nextGeneration = thread.postAndWait(new Callable<Integer>() {
113            @Override
114            public Integer call() throws Exception {
115                return thread.getGeneration();
116            }
117        }, DEFAULT_TIMEOUT);
118        assertNotNull(nextGeneration);
119        assertNotEquals(generation.intValue(), nextGeneration.intValue());
120    }
121
122    @Test
123    public void testReuseSameThread() throws InterruptedException {
124        final int destructAfterLastActivityInMs = 300;
125        final SelfDestructiveThread thread = new SelfDestructiveThread(
126                "test", Process.THREAD_PRIORITY_BACKGROUND, destructAfterLastActivityInMs);
127        Integer generation = thread.postAndWait(new Callable<Integer>() {
128            @Override
129            public Integer call() throws Exception {
130                return thread.getGeneration();
131            }
132        }, DEFAULT_TIMEOUT);
133        assertNotNull(generation);
134        Integer nextGeneration = thread.postAndWait(new Callable<Integer>() {
135            @Override
136            public Integer call() throws Exception {
137                return thread.getGeneration();
138            }
139        }, DEFAULT_TIMEOUT);
140        assertNotNull(nextGeneration);
141        waitUntilDestruction(thread, DEFAULT_TIMEOUT);
142        assertEquals(generation.intValue(), nextGeneration.intValue());
143    }
144
145    @Test
146    public void testReuseSameThread_Multiple() throws InterruptedException {
147        final int destructAfterLastActivityInMs = 300;
148        final SelfDestructiveThread thread = new SelfDestructiveThread(
149                "test", Process.THREAD_PRIORITY_BACKGROUND, destructAfterLastActivityInMs);
150        Integer generation = thread.postAndWait(new Callable<Integer>() {
151            @Override
152            public Integer call() throws Exception {
153                return thread.getGeneration();
154            }
155        }, DEFAULT_TIMEOUT);
156        assertNotNull(generation);
157        int firstGeneration = generation.intValue();
158        for (int i = 0; i < 10; ++i) {
159            // Less than renewal duration, so that the same thread must be used.
160            waitMillis(destructAfterLastActivityInMs / 2);
161            Integer nextGeneration = thread.postAndWait(new Callable<Integer>() {
162                @Override
163                public Integer call() throws Exception {
164                    return thread.getGeneration();
165                }
166            }, DEFAULT_TIMEOUT);
167            assertNotNull(nextGeneration);
168            assertEquals(firstGeneration, nextGeneration.intValue());
169        }
170        waitUntilDestruction(thread, DEFAULT_TIMEOUT);
171    }
172
173    @Test
174    public void testTimeout() {
175        final int destructAfterLastActivityInMs = 300;
176        final SelfDestructiveThread thread = new SelfDestructiveThread(
177                "test", Process.THREAD_PRIORITY_BACKGROUND, destructAfterLastActivityInMs);
178
179        final int timeoutMs = 300;
180        try {
181            thread.postAndWait(new Callable<Object>() {
182                @Override
183                public Object call() throws Exception {
184                    waitMillis(timeoutMs * 3);  // Wait longer than timeout.
185                    return new Object();
186                }
187            }, timeoutMs);
188            fail();
189        } catch (InterruptedException e) {
190             // pass
191        }
192    }
193
194    private class WaitableReplyCallback implements ReplyCallback<Integer> {
195        private final ReentrantLock mLock = new ReentrantLock();
196        private final Condition mCond = mLock.newCondition();
197
198        @GuardedBy("mLock")
199        private Integer mValue;
200
201        private static final int NOT_STARTED = 0;
202        private static final int WAITING = 1;
203        private static final int FINISHED = 2;
204        private static final int TIMEOUT = 3;
205        @GuardedBy("mLock")
206        int mState = NOT_STARTED;
207
208        @Override
209        public void onReply(Integer value) {
210            mLock.lock();
211            try {
212                if (mState != TIMEOUT) {
213                    mValue = value;
214                    mState = FINISHED;
215                }
216                mCond.signalAll();
217            } finally {
218                mLock.unlock();
219            }
220        }
221
222        public Integer waitUntil(long timeoutMillis) {
223            mLock.lock();
224            try {
225                if (mState == FINISHED) {
226                    return mValue;
227                }
228                mState = WAITING;
229                long remaining = TimeUnit.MILLISECONDS.toNanos(timeoutMillis);
230                while (mState == WAITING) {
231                    try {
232                        remaining = mCond.awaitNanos(remaining);
233                    } catch (InterruptedException e) {
234                        // Ignore.
235                    }
236                    if (mState == FINISHED) {
237                        return mValue;
238                    }
239                    if (remaining <= 0) {
240                        mState = TIMEOUT;
241                        fail("Timeout");
242                    }
243                }
244                throw new IllegalStateException("mState becomes unexpected state");
245            } finally {
246                mLock.unlock();
247            }
248        }
249    }
250
251    @Test
252    public void testPostAndReply() {
253        final int destructAfterLastActivityInMs = 300;
254        final Integer expectedResult = 123;
255
256        final Callable<Integer> callable = new Callable<Integer>() {
257            @Override
258            public Integer call() throws Exception {
259                return expectedResult;
260            }
261        };
262        final WaitableReplyCallback reply = new WaitableReplyCallback();
263        final SelfDestructiveThread thread = new SelfDestructiveThread(
264                "test", Process.THREAD_PRIORITY_BACKGROUND, destructAfterLastActivityInMs);
265        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
266            @Override
267            public void run() {
268                thread.postAndReply(callable, reply);
269            }
270        });
271
272        assertEquals(expectedResult, reply.waitUntil(DEFAULT_TIMEOUT));
273    }
274}
275