Home | History | Annotate | Download | only in utils
      1 /*
      2  * Copyright (C) 2014 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 package android.hardware.camera2.utils;
     17 
     18 import android.util.Log;
     19 
     20 import java.util.HashSet;
     21 import java.util.Set;
     22 import java.util.concurrent.Executor;
     23 
     24 import static com.android.internal.util.Preconditions.*;
     25 
     26 /**
     27  * Keep track of multiple concurrent tasks starting and finishing by their key;
     28  * allow draining existing tasks and figuring out when all tasks have finished
     29  * (and new ones won't begin).
     30  *
     31  * <p>The initial state is to allow all tasks to be started and finished. A task may only be started
     32  * once, after which it must be finished before starting again. Likewise, a task may only be
     33  * finished once, after which it must be started before finishing again. It is okay to finish a
     34  * task before starting it due to different threads handling starting and finishing.</p>
     35  *
     36  * <p>When draining begins, no more new tasks can be started. This guarantees that at some
     37  * point when all the tasks are finished there will be no more collective new tasks,
     38  * at which point the {@link DrainListener#onDrained} callback will be invoked.</p>
     39  *
     40  *
     41  * @param <T>
     42  *          a type for the key that will represent tracked tasks;
     43  *          must implement {@code Object#equals}
     44  */
     45 public class TaskDrainer<T> {
     46     /**
     47      * Fired asynchronously after draining has begun with {@link TaskDrainer#beginDrain}
     48      * <em>and</em> all tasks that were started have finished.
     49      */
     50     public interface DrainListener {
     51         /** All tasks have fully finished draining; there will be no more pending tasks. */
     52         public void onDrained();
     53     }
     54 
     55     private static final String TAG = "TaskDrainer";
     56     private final boolean DEBUG = false;
     57 
     58     private final Executor mExecutor;
     59     private final DrainListener mListener;
     60     private final String mName;
     61 
     62     /** Set of tasks which have been started but not yet finished with #taskFinished */
     63     private final Set<T> mTaskSet = new HashSet<T>();
     64     /**
     65      * Set of tasks which have been finished but not yet started with #taskStarted. This may happen
     66      * if taskStarted and taskFinished are called from two different threads.
     67      */
     68     private final Set<T> mEarlyFinishedTaskSet = new HashSet<T>();
     69     private final Object mLock = new Object();
     70 
     71     private boolean mDraining = false;
     72     private boolean mDrainFinished = false;
     73 
     74     /**
     75      * Create a new task drainer; {@code onDrained} callbacks will be posted to the listener
     76      * via the {@code executor}.
     77      *
     78      * @param executor a non-{@code null} executor to use for listener execution
     79      * @param listener a non-{@code null} listener where {@code onDrained} will be called
     80      */
     81     public TaskDrainer(Executor executor, DrainListener listener) {
     82         mExecutor = checkNotNull(executor, "executor must not be null");
     83         mListener = checkNotNull(listener, "listener must not be null");
     84         mName = null;
     85     }
     86 
     87     /**
     88      * Create a new task drainer; {@code onDrained} callbacks will be posted to the listener
     89      * via the {@code executor}.
     90      *
     91      * @param executor a non-{@code null} executor to use for listener execution
     92      * @param listener a non-{@code null} listener where {@code onDrained} will be called
     93      * @param name an optional name used for debug logging
     94      */
     95     public TaskDrainer(Executor executor, DrainListener listener, String name) {
     96         mExecutor = checkNotNull(executor, "executor must not be null");
     97         mListener = checkNotNull(listener, "listener must not be null");
     98         mName = name;
     99     }
    100 
    101     /**
    102      * Mark an asynchronous task as having started.
    103      *
    104      * <p>A task cannot be started more than once without first having finished. Once
    105      * draining begins with {@link #beginDrain}, no new tasks can be started.</p>
    106      *
    107      * @param task a key to identify a task
    108      *
    109      * @see #taskFinished
    110      * @see #beginDrain
    111      *
    112      * @throws IllegalStateException
    113      *          If attempting to start a task which is already started (and not finished),
    114      *          or if attempting to start a task after draining has begun.
    115      */
    116     public void taskStarted(T task) {
    117         synchronized (mLock) {
    118             if (DEBUG) {
    119                 Log.v(TAG + "[" + mName + "]", "taskStarted " + task);
    120             }
    121 
    122             if (mDraining) {
    123                 throw new IllegalStateException("Can't start more tasks after draining has begun");
    124             }
    125 
    126             // Try to remove the task from the early finished set.
    127             if (!mEarlyFinishedTaskSet.remove(task)) {
    128                 // The task is not finished early. Add it to the started set.
    129                 if (!mTaskSet.add(task)) {
    130                     throw new IllegalStateException("Task " + task + " was already started");
    131                 }
    132             }
    133         }
    134     }
    135 
    136 
    137     /**
    138      * Mark an asynchronous task as having finished.
    139      *
    140      * <p>A task cannot be finished more than once without first having started.</p>
    141      *
    142      * @param task a key to identify a task
    143      *
    144      * @see #taskStarted
    145      * @see #beginDrain
    146      *
    147      * @throws IllegalStateException
    148      *          If attempting to finish a task which is already finished (and not started),
    149      */
    150     public void taskFinished(T task) {
    151         synchronized (mLock) {
    152             if (DEBUG) {
    153                 Log.v(TAG + "[" + mName + "]", "taskFinished " + task);
    154             }
    155 
    156             // Try to remove the task from started set.
    157             if (!mTaskSet.remove(task)) {
    158                 // Task is not started yet. Add it to the early finished set.
    159                 if (!mEarlyFinishedTaskSet.add(task)) {
    160                     throw new IllegalStateException("Task " + task + " was already finished");
    161                 }
    162             }
    163 
    164             // If this is the last finished task and draining has already begun, fire #onDrained
    165             checkIfDrainFinished();
    166         }
    167     }
    168 
    169     /**
    170      * Do not allow any more tasks to be started; once all existing started tasks are finished,
    171      * fire the {@link DrainListener#onDrained} callback asynchronously.
    172      *
    173      * <p>This operation is idempotent; calling it more than once has no effect.</p>
    174      */
    175     public void beginDrain() {
    176         synchronized (mLock) {
    177             if (!mDraining) {
    178                 if (DEBUG) {
    179                     Log.v(TAG + "[" + mName + "]", "beginDrain started");
    180                 }
    181 
    182                 mDraining = true;
    183 
    184                 // If all tasks that had started had already finished by now, fire #onDrained
    185                 checkIfDrainFinished();
    186             } else {
    187                 if (DEBUG) {
    188                     Log.v(TAG + "[" + mName + "]", "beginDrain ignored");
    189                 }
    190             }
    191         }
    192     }
    193 
    194     private void checkIfDrainFinished() {
    195         if (mTaskSet.isEmpty() && mDraining && !mDrainFinished) {
    196             mDrainFinished = true;
    197             postDrained();
    198         }
    199     }
    200 
    201     private void postDrained() {
    202         mExecutor.execute(() -> {
    203                 if (DEBUG) {
    204                     Log.v(TAG + "[" + mName + "]", "onDrained");
    205                 }
    206 
    207                 mListener.onDrained();
    208         });
    209     }
    210 }
    211