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.arch.core.executor; 18 19 import androidx.annotation.NonNull; 20 import androidx.annotation.RestrictTo; 21 22 import java.util.ArrayList; 23 import java.util.Collections; 24 import java.util.List; 25 import java.util.concurrent.CountDownLatch; 26 import java.util.concurrent.ExecutorService; 27 import java.util.concurrent.Executors; 28 import java.util.concurrent.ThreadFactory; 29 import java.util.concurrent.TimeUnit; 30 31 /** 32 * A TaskExecutor that has a real thread for main thread operations and can wait for execution etc. 33 * 34 * @hide 35 */ 36 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 37 public class TaskExecutorWithFakeMainThread extends TaskExecutor { 38 private List<Throwable> mCaughtExceptions = Collections.synchronizedList(new ArrayList 39 <Throwable>()); 40 41 private ExecutorService mIOService; 42 43 private Thread mMainThread; 44 private final int mIOThreadCount; 45 46 private ExecutorService mMainThreadService = 47 Executors.newSingleThreadExecutor(new ThreadFactory() { 48 @Override 49 public Thread newThread(@NonNull final Runnable r) { 50 mMainThread = new LoggingThread(r); 51 return mMainThread; 52 } 53 }); 54 55 public TaskExecutorWithFakeMainThread(int ioThreadCount) { 56 mIOThreadCount = ioThreadCount; 57 mIOService = Executors.newFixedThreadPool(ioThreadCount, new ThreadFactory() { 58 @Override 59 public Thread newThread(@NonNull Runnable r) { 60 return new LoggingThread(r); 61 } 62 }); 63 } 64 65 @Override 66 public void executeOnDiskIO(Runnable runnable) { 67 mIOService.execute(runnable); 68 } 69 70 @Override 71 public void postToMainThread(Runnable runnable) { 72 // Tasks in SingleThreadExecutor are guaranteed to execute sequentially, 73 // and no more than one task will be active at any given time. 74 // So if we call this method from the main thread, new task will be scheduled, 75 // which is equivalent to post. 76 mMainThreadService.execute(runnable); 77 } 78 79 @Override 80 public boolean isMainThread() { 81 return Thread.currentThread() == mMainThread; 82 } 83 84 List<Throwable> getErrors() { 85 return mCaughtExceptions; 86 } 87 88 @SuppressWarnings("SameParameterValue") 89 void shutdown(int timeoutInSeconds) throws InterruptedException { 90 mMainThreadService.shutdown(); 91 mIOService.shutdown(); 92 mMainThreadService.awaitTermination(timeoutInSeconds, TimeUnit.SECONDS); 93 mIOService.awaitTermination(timeoutInSeconds, TimeUnit.SECONDS); 94 } 95 96 /** 97 * Drains tasks at the given time limit 98 * @param seconds Number of seconds to wait 99 * @throws InterruptedException 100 */ 101 public void drainTasks(int seconds) throws InterruptedException { 102 if (isMainThread()) { 103 throw new IllegalStateException(); 104 } 105 final CountDownLatch enterLatch = new CountDownLatch(mIOThreadCount); 106 final CountDownLatch exitLatch = new CountDownLatch(1); 107 for (int i = 0; i < mIOThreadCount; i++) { 108 executeOnDiskIO(new Runnable() { 109 @Override 110 public void run() { 111 enterLatch.countDown(); 112 try { 113 exitLatch.await(); 114 } catch (InterruptedException e) { 115 throw new RuntimeException(e); 116 } 117 } 118 }); 119 } 120 121 final CountDownLatch mainLatch = new CountDownLatch(1); 122 postToMainThread(new Runnable() { 123 @Override 124 public void run() { 125 mainLatch.countDown(); 126 } 127 }); 128 if (!enterLatch.await(seconds, TimeUnit.SECONDS)) { 129 throw new AssertionError("Could not drain IO tasks in " + seconds 130 + " seconds"); 131 } 132 exitLatch.countDown(); 133 if (!mainLatch.await(seconds, TimeUnit.SECONDS)) { 134 throw new AssertionError("Could not drain UI tasks in " + seconds 135 + " seconds"); 136 } 137 } 138 139 @SuppressWarnings("WeakerAccess") 140 class LoggingThread extends Thread { 141 LoggingThread(final Runnable target) { 142 super(new Runnable() { 143 @Override 144 public void run() { 145 try { 146 target.run(); 147 } catch (Throwable t) { 148 mCaughtExceptions.add(t); 149 } 150 } 151 }); 152 } 153 } 154 } 155