Home | History | Annotate | Download | only in executor
      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