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