Implement a simple worker thread

Bug: 166800618
Bug: 175070939
Test: atest --host android.hardware.biometrics.fingerprint.WorkerThreadTest
Change-Id: Ic84efbde21d0997450585078b311610fe752fa88
diff --git a/biometrics/fingerprint/aidl/default/Android.bp b/biometrics/fingerprint/aidl/default/Android.bp
index 69fb2e3..24087cf 100644
--- a/biometrics/fingerprint/aidl/default/Android.bp
+++ b/biometrics/fingerprint/aidl/default/Android.bp
@@ -17,3 +17,16 @@
         "android.hardware.biometrics.common-V1-ndk_platform",
     ],
 }
+
+cc_test_host {
+    name: "android.hardware.biometrics.fingerprint.WorkerThreadTest",
+    local_include_dirs: ["include"],
+    srcs: [
+        "tests/WorkerThreadTest.cpp",
+        "WorkerThread.cpp",
+    ],
+    shared_libs: [
+        "libcutils",
+    ],
+    test_suites: ["general-tests"],
+}
diff --git a/biometrics/fingerprint/aidl/default/WorkerThread.cpp b/biometrics/fingerprint/aidl/default/WorkerThread.cpp
new file mode 100644
index 0000000..512efb8
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/WorkerThread.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include "WorkerThread.h"
+
+namespace aidl::android::hardware::biometrics::fingerprint {
+
+// It's important that mThread is initialized after everything else because it runs a member
+// function that may use any member of this class.
+WorkerThread::WorkerThread(size_t maxQueueSize)
+    : mMaxSize(maxQueueSize),
+      mIsDestructing(false),
+      mQueue(),
+      mQueueMutex(),
+      mQueueCond(),
+      mThread(&WorkerThread::threadFunc, this) {}
+
+WorkerThread::~WorkerThread() {
+    // This is a signal for threadFunc to terminate as soon as possible, and a hint for schedule
+    // that it doesn't need to do any work.
+    mIsDestructing = true;
+    mQueueCond.notify_all();
+    mThread.join();
+}
+
+bool WorkerThread::schedule(Task&& task) {
+    if (mIsDestructing) {
+        return false;
+    }
+
+    std::unique_lock<std::mutex> lock(mQueueMutex);
+    if (mQueue.size() >= mMaxSize) {
+        return false;
+    }
+    mQueue.push_back(std::move(task));
+    lock.unlock();
+    mQueueCond.notify_one();
+    return true;
+}
+
+void WorkerThread::threadFunc() {
+    while (!mIsDestructing) {
+        std::unique_lock<std::mutex> lock(mQueueMutex);
+        mQueueCond.wait(lock, [this] { return !mQueue.empty() || mIsDestructing; });
+        if (mIsDestructing) {
+            return;
+        }
+        Task task = std::move(mQueue.front());
+        mQueue.pop_front();
+        lock.unlock();
+        task();
+    }
+}
+
+}  // namespace aidl::android::hardware::biometrics::fingerprint
diff --git a/biometrics/fingerprint/aidl/default/include/WorkerThread.h b/biometrics/fingerprint/aidl/default/include/WorkerThread.h
new file mode 100644
index 0000000..49104c8
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/include/WorkerThread.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#pragma once
+
+#include <mutex>
+#include <optional>
+#include <queue>
+#include <thread>
+
+namespace aidl::android::hardware::biometrics::fingerprint {
+
+using Task = std::function<void()>;
+
+// A class that encapsulates a worker thread and a task queue, and provides a convenient interface
+// for a Session to schedule its tasks for asynchronous execution.
+class WorkerThread final {
+  public:
+    // Internally creates a queue that cannot exceed maxQueueSize elements and a new thread that
+    // polls the queue for tasks until this instance is destructed.
+    explicit WorkerThread(size_t maxQueueSize);
+
+    // Unblocks the internal queue and calls join on the internal thread allowing it to gracefully
+    // exit.
+    ~WorkerThread();
+
+    // Disallow copying this class.
+    WorkerThread(const WorkerThread&) = delete;
+    WorkerThread& operator=(const WorkerThread&) = delete;
+
+    // Also disable moving this class to simplify implementation.
+    WorkerThread(WorkerThread&&) = delete;
+    WorkerThread& operator=(WorkerThread&&) = delete;
+
+    // If the internal queue is not full, pushes a task at the end of the queue and returns true.
+    // Otherwise, returns false. If the queue is busy, blocks until it becomes available.
+    bool schedule(Task&& task);
+
+  private:
+    // The function that runs on the internal thread. Sequentially runs the available tasks from
+    // the queue. If the queue is empty, waits until a new task is added. If the worker is being
+    // destructed, finishes its current task and gracefully exits.
+    void threadFunc();
+
+    // The maximum size that the queue is allowed to expand to.
+    size_t mMaxSize;
+
+    // Whether the destructor was called. If true, tells threadFunc to exit as soon as possible, and
+    // tells schedule to avoid doing any work.
+    std::atomic<bool> mIsDestructing;
+
+    // Queue that's guarded by mQueueMutex and mQueueCond.
+    std::deque<Task> mQueue;
+    std::mutex mQueueMutex;
+    std::condition_variable mQueueCond;
+
+    // The internal thread that works on the tasks from the queue.
+    std::thread mThread;
+};
+
+}  // namespace aidl::android::hardware::biometrics::fingerprint
diff --git a/biometrics/fingerprint/aidl/default/tests/WorkerThreadTest.cpp b/biometrics/fingerprint/aidl/default/tests/WorkerThreadTest.cpp
new file mode 100644
index 0000000..ba901ad
--- /dev/null
+++ b/biometrics/fingerprint/aidl/default/tests/WorkerThreadTest.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include <algorithm>
+#include <chrono>
+#include <future>
+#include <thread>
+
+#include <gtest/gtest.h>
+
+#include "WorkerThread.h"
+
+namespace {
+
+using aidl::android::hardware::biometrics::fingerprint::WorkerThread;
+using namespace std::chrono_literals;
+
+TEST(WorkerThreadTest, ScheduleReturnsTrueWhenQueueHasSpace) {
+    WorkerThread worker(1 /*maxQueueSize*/);
+    for (int i = 0; i < 100; ++i) {
+        EXPECT_TRUE(worker.schedule([] {}));
+        // Allow enough time for the previous task to be processed.
+        std::this_thread::sleep_for(2ms);
+    }
+}
+
+TEST(WorkerThreadTest, ScheduleReturnsFalseWhenQueueIsFull) {
+    WorkerThread worker(2 /*maxQueueSize*/);
+    // Add a long-running task.
+    worker.schedule([] { std::this_thread::sleep_for(1s); });
+
+    // Allow enough time for the worker to start working on the previous task.
+    std::this_thread::sleep_for(2ms);
+
+    // Fill the worker's queue to the maximum.
+    worker.schedule([] {});
+    worker.schedule([] {});
+
+    EXPECT_FALSE(worker.schedule([] {}));
+}
+
+TEST(WorkerThreadTest, TasksExecuteInOrder) {
+    constexpr int NUM_TASKS = 10000;
+    WorkerThread worker(NUM_TASKS);
+
+    std::vector<int> results;
+    for (int i = 0; i < NUM_TASKS; ++i) {
+        worker.schedule([&results, i] {
+            // Delay tasks differently to provoke races.
+            std::this_thread::sleep_for(std::chrono::nanoseconds(100 - i % 100));
+            // Unguarded write to results to provoke races.
+            results.push_back(i);
+        });
+    }
+
+    std::promise<void> promise;
+    auto future = promise.get_future();
+
+    // Schedule a special task to signal when all of the tasks are finished.
+    worker.schedule([&promise] { promise.set_value(); });
+    auto status = future.wait_for(1s);
+    ASSERT_EQ(status, std::future_status::ready);
+
+    ASSERT_EQ(results.size(), NUM_TASKS);
+    EXPECT_TRUE(std::is_sorted(results.begin(), results.end()));
+}
+
+TEST(WorkerThreadTest, ExecutionStopsAfterWorkerIsDestroyed) {
+    std::promise<void> promise1;
+    std::promise<void> promise2;
+    auto future1 = promise1.get_future();
+    auto future2 = promise2.get_future();
+
+    {
+        WorkerThread worker(2 /*maxQueueSize*/);
+        worker.schedule([&promise1] {
+            promise1.set_value();
+            std::this_thread::sleep_for(200ms);
+        });
+        worker.schedule([&promise2] { promise2.set_value(); });
+
+        // Make sure the first task is executing.
+        auto status1 = future1.wait_for(1s);
+        ASSERT_EQ(status1, std::future_status::ready);
+    }
+
+    // The second task should never execute.
+    auto status2 = future2.wait_for(1s);
+    EXPECT_EQ(status2, std::future_status::timeout);
+}
+
+}  // namespace