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