• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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. */
onDrained()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      */
TaskDrainer(Executor executor, DrainListener listener)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      */
TaskDrainer(Executor executor, DrainListener listener, String name)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      */
taskStarted(T task)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      */
taskFinished(T task)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      */
beginDrain()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 
checkIfDrainFinished()194     private void checkIfDrainFinished() {
195         if (mTaskSet.isEmpty() && mDraining && !mDrainFinished) {
196             mDrainFinished = true;
197             postDrained();
198         }
199     }
200 
postDrained()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