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