211 lines
7.6 KiB
Java
211 lines
7.6 KiB
Java
/*
|
|
* Copyright (C) 2014 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package android.hardware.camera2.utils;
|
|
|
|
import android.util.Log;
|
|
|
|
import java.util.HashSet;
|
|
import java.util.Set;
|
|
import java.util.concurrent.Executor;
|
|
|
|
import static com.android.internal.util.Preconditions.*;
|
|
|
|
/**
|
|
* Keep track of multiple concurrent tasks starting and finishing by their key;
|
|
* allow draining existing tasks and figuring out when all tasks have finished
|
|
* (and new ones won't begin).
|
|
*
|
|
* <p>The initial state is to allow all tasks to be started and finished. A task may only be started
|
|
* once, after which it must be finished before starting again. Likewise, a task may only be
|
|
* finished once, after which it must be started before finishing again. It is okay to finish a
|
|
* task before starting it due to different threads handling starting and finishing.</p>
|
|
*
|
|
* <p>When draining begins, no more new tasks can be started. This guarantees that at some
|
|
* point when all the tasks are finished there will be no more collective new tasks,
|
|
* at which point the {@link DrainListener#onDrained} callback will be invoked.</p>
|
|
*
|
|
*
|
|
* @param <T>
|
|
* a type for the key that will represent tracked tasks;
|
|
* must implement {@code Object#equals}
|
|
*/
|
|
public class TaskDrainer<T> {
|
|
/**
|
|
* Fired asynchronously after draining has begun with {@link TaskDrainer#beginDrain}
|
|
* <em>and</em> all tasks that were started have finished.
|
|
*/
|
|
public interface DrainListener {
|
|
/** All tasks have fully finished draining; there will be no more pending tasks. */
|
|
public void onDrained();
|
|
}
|
|
|
|
private static final String TAG = "TaskDrainer";
|
|
private final boolean DEBUG = false;
|
|
|
|
private final Executor mExecutor;
|
|
private final DrainListener mListener;
|
|
private final String mName;
|
|
|
|
/** Set of tasks which have been started but not yet finished with #taskFinished */
|
|
private final Set<T> mTaskSet = new HashSet<T>();
|
|
/**
|
|
* Set of tasks which have been finished but not yet started with #taskStarted. This may happen
|
|
* if taskStarted and taskFinished are called from two different threads.
|
|
*/
|
|
private final Set<T> mEarlyFinishedTaskSet = new HashSet<T>();
|
|
private final Object mLock = new Object();
|
|
|
|
private boolean mDraining = false;
|
|
private boolean mDrainFinished = false;
|
|
|
|
/**
|
|
* Create a new task drainer; {@code onDrained} callbacks will be posted to the listener
|
|
* via the {@code executor}.
|
|
*
|
|
* @param executor a non-{@code null} executor to use for listener execution
|
|
* @param listener a non-{@code null} listener where {@code onDrained} will be called
|
|
*/
|
|
public TaskDrainer(Executor executor, DrainListener listener) {
|
|
mExecutor = checkNotNull(executor, "executor must not be null");
|
|
mListener = checkNotNull(listener, "listener must not be null");
|
|
mName = null;
|
|
}
|
|
|
|
/**
|
|
* Create a new task drainer; {@code onDrained} callbacks will be posted to the listener
|
|
* via the {@code executor}.
|
|
*
|
|
* @param executor a non-{@code null} executor to use for listener execution
|
|
* @param listener a non-{@code null} listener where {@code onDrained} will be called
|
|
* @param name an optional name used for debug logging
|
|
*/
|
|
public TaskDrainer(Executor executor, DrainListener listener, String name) {
|
|
mExecutor = checkNotNull(executor, "executor must not be null");
|
|
mListener = checkNotNull(listener, "listener must not be null");
|
|
mName = name;
|
|
}
|
|
|
|
/**
|
|
* Mark an asynchronous task as having started.
|
|
*
|
|
* <p>A task cannot be started more than once without first having finished. Once
|
|
* draining begins with {@link #beginDrain}, no new tasks can be started.</p>
|
|
*
|
|
* @param task a key to identify a task
|
|
*
|
|
* @see #taskFinished
|
|
* @see #beginDrain
|
|
*
|
|
* @throws IllegalStateException
|
|
* If attempting to start a task which is already started (and not finished),
|
|
* or if attempting to start a task after draining has begun.
|
|
*/
|
|
public void taskStarted(T task) {
|
|
synchronized (mLock) {
|
|
if (DEBUG) {
|
|
Log.v(TAG + "[" + mName + "]", "taskStarted " + task);
|
|
}
|
|
|
|
if (mDraining) {
|
|
throw new IllegalStateException("Can't start more tasks after draining has begun");
|
|
}
|
|
|
|
// Try to remove the task from the early finished set.
|
|
if (!mEarlyFinishedTaskSet.remove(task)) {
|
|
// The task is not finished early. Add it to the started set.
|
|
if (!mTaskSet.add(task)) {
|
|
throw new IllegalStateException("Task " + task + " was already started");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Mark an asynchronous task as having finished.
|
|
*
|
|
* <p>A task cannot be finished more than once without first having started.</p>
|
|
*
|
|
* @param task a key to identify a task
|
|
*
|
|
* @see #taskStarted
|
|
* @see #beginDrain
|
|
*
|
|
* @throws IllegalStateException
|
|
* If attempting to finish a task which is already finished (and not started),
|
|
*/
|
|
public void taskFinished(T task) {
|
|
synchronized (mLock) {
|
|
if (DEBUG) {
|
|
Log.v(TAG + "[" + mName + "]", "taskFinished " + task);
|
|
}
|
|
|
|
// Try to remove the task from started set.
|
|
if (!mTaskSet.remove(task)) {
|
|
// Task is not started yet. Add it to the early finished set.
|
|
if (!mEarlyFinishedTaskSet.add(task)) {
|
|
throw new IllegalStateException("Task " + task + " was already finished");
|
|
}
|
|
}
|
|
|
|
// If this is the last finished task and draining has already begun, fire #onDrained
|
|
checkIfDrainFinished();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Do not allow any more tasks to be started; once all existing started tasks are finished,
|
|
* fire the {@link DrainListener#onDrained} callback asynchronously.
|
|
*
|
|
* <p>This operation is idempotent; calling it more than once has no effect.</p>
|
|
*/
|
|
public void beginDrain() {
|
|
synchronized (mLock) {
|
|
if (!mDraining) {
|
|
if (DEBUG) {
|
|
Log.v(TAG + "[" + mName + "]", "beginDrain started");
|
|
}
|
|
|
|
mDraining = true;
|
|
|
|
// If all tasks that had started had already finished by now, fire #onDrained
|
|
checkIfDrainFinished();
|
|
} else {
|
|
if (DEBUG) {
|
|
Log.v(TAG + "[" + mName + "]", "beginDrain ignored");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void checkIfDrainFinished() {
|
|
if (mTaskSet.isEmpty() && mDraining && !mDrainFinished) {
|
|
mDrainFinished = true;
|
|
postDrained();
|
|
}
|
|
}
|
|
|
|
private void postDrained() {
|
|
mExecutor.execute(() -> {
|
|
if (DEBUG) {
|
|
Log.v(TAG + "[" + mName + "]", "onDrained");
|
|
}
|
|
|
|
mListener.onDrained();
|
|
});
|
|
}
|
|
}
|