NNAPI Concurrent Query Management -- HAL and VTS update

The NNAPI requires requests on a model to be asynchronously
processed. This CL implements a basic Event that can later
be used to block the runtime thread until the asynchronous
request has completed.

Bug: 63905942
Test: VtsHalNeuralnetworksV1_0TargetTest (32-bit, 64-bit) with sample driver enabled by cherry-pick
      frameworks/ml/nn/runtime/test with and without sample driver enabled
Change-Id: Ie27a574aaaac312e7cbb731750f9c06278357a1c
diff --git a/neuralnetworks/1.0/vts/functional/Android.bp b/neuralnetworks/1.0/vts/functional/Android.bp
index d76b2c3..1efff0e 100644
--- a/neuralnetworks/1.0/vts/functional/Android.bp
+++ b/neuralnetworks/1.0/vts/functional/Android.bp
@@ -16,7 +16,10 @@
 
 cc_test {
     name: "VtsHalNeuralnetworksV1_0TargetTest",
-    srcs: ["VtsHalNeuralnetworksV1_0TargetTest.cpp"],
+    srcs: [
+        "Event.cpp",
+        "VtsHalNeuralnetworksV1_0TargetTest.cpp",
+    ],
     defaults: ["VtsHalTargetTestDefaults"],
     static_libs: [
         "android.hardware.neuralnetworks@1.0",
diff --git a/neuralnetworks/1.0/vts/functional/Event.cpp b/neuralnetworks/1.0/vts/functional/Event.cpp
new file mode 100644
index 0000000..0fab86b
--- /dev/null
+++ b/neuralnetworks/1.0/vts/functional/Event.cpp
@@ -0,0 +1,76 @@
+#include "Event.h"
+#include <android-base/logging.h>
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+namespace V1_0 {
+namespace implementation {
+
+Event::Event() : mStatus(Status::WAITING) {}
+
+Event::~Event() {
+    if (mThread.joinable()) {
+        mThread.join();
+    }
+}
+
+Return<void> Event::notify(ReturnedStatus status) {
+    {
+        std::lock_guard<std::mutex> lock(mMutex);
+        mStatus = status == ReturnedStatus::SUCCESS ? Status::SUCCESS : Status::ERROR;
+        if (mStatus == Status::SUCCESS && mCallback != nullptr) {
+            bool success = mCallback();
+            if (!success) {
+                LOG(ERROR) << "Event::notify -- callback failed";
+            }
+        }
+    }
+    mCondition.notify_all();
+    return Void();
+}
+
+Event::Status Event::poll() {
+    std::lock_guard<std::mutex> lock(mMutex);
+    return mStatus;
+}
+
+Event::Status Event::wait() {
+    std::unique_lock<std::mutex> lock(mMutex);
+    mCondition.wait(lock, [this]{return mStatus != Status::WAITING;});
+    return mStatus;
+}
+
+bool Event::on_finish(std::function<bool(void)> callback) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (mCallback != nullptr) {
+        LOG(ERROR) << "Event::on_finish -- a callback has already been bound to this event";
+        return false;
+    }
+    if (callback == nullptr) {
+        LOG(ERROR) << "Event::on_finish -- the new callback is invalid";
+        return false;
+    }
+    mCallback = std::move(callback);
+    return true;
+}
+
+bool Event::bind_thread(std::thread&& asyncThread) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (mThread.joinable()) {
+        LOG(ERROR) << "Event::bind_thread -- a thread has already been bound to this event";
+        return false;
+    }
+    if (!asyncThread.joinable()) {
+        LOG(ERROR) << "Event::bind_thread -- the new thread is not joinable";
+        return false;
+    }
+    mThread = std::move(asyncThread);
+    return true;
+}
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace neuralnetworks
+}  // namespace hardware
+}  // namespace android
diff --git a/neuralnetworks/1.0/vts/functional/Event.h b/neuralnetworks/1.0/vts/functional/Event.h
new file mode 100644
index 0000000..2e19585
--- /dev/null
+++ b/neuralnetworks/1.0/vts/functional/Event.h
@@ -0,0 +1,192 @@
+#ifndef ANDROID_HARDWARE_NEURALNETWORKS_V1_0_EVENT_H
+#define ANDROID_HARDWARE_NEURALNETWORKS_V1_0_EVENT_H
+
+#include <android/hardware/neuralnetworks/1.0/IEvent.h>
+#include <chrono>
+#include <condition_variable>
+#include <functional>
+#include <hidl/MQDescriptor.h>
+#include <hidl/Status.h>
+#include <mutex>
+#include <thread>
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+namespace V1_0 {
+namespace implementation {
+
+using ::android::hardware::hidl_array;
+using ::android::hardware::hidl_memory;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::sp;
+
+using ReturnedStatus = ::android::hardware::neuralnetworks::V1_0::Status;
+
+/**
+ * The Event class is used internally by the Neuralnetworks runtime to
+ * synchronize between different threads. An asynchronous task is launched
+ * paired with an event object. When a client thread requires the output being
+ * processed by the asynchronous task, the client thread can wait for the result
+ * and be blocked until it has completed or a timeout condition has been
+ * reached, or poll the result periodically. Both poll and wait* may safely be
+ * called concurrently, even on the same event. When the server thread has
+ * completed, it should immediately call "notify" to indicate the corresponding
+ * output has been produced and awaken any client threads waiting on the event.
+ *
+ * This class exists to enable synchronization across HIDL. When synchronization
+ * is only required in the same process, consider using std::future, std::mutex,
+ * std::condition_variable, or std::experimental::latch instead.
+ */
+struct Event : public IEvent {
+    Event();
+    ~Event() override;
+
+    /**
+     * Event::Status::WAITING -- The corresponding asynchronous execution has
+     *                           not yet finished.
+     * Event::Status::SUCCESS -- The corresponding asynchronous execution has
+     *                           succeeded and the output is ready to be
+     *                           consumed.
+     * Event::Status::TIMEOUT -- The calling thread has waited longer than the
+     *                           user has specified. This only applies to the
+     *                           methods Event::wait_for and Event::wait_until.
+     * Event::Status::ERROR   -- The corresponding asynchronous execution has
+     *                           failed to properly execute.
+     */
+    enum class Status : uint32_t {
+        WAITING,
+        SUCCESS,
+        TIMEOUT,
+        ERROR,
+    };
+
+    /**
+     * IEvent::notify marks the event with the return status of the
+     * asynchronous call the event is paired with and enables all
+     * prior and future wait calls on the Event object to proceed. The
+     * call to IEvent::notify happens before any wait* calls on
+     * this event return (except in the case of TIMEOUT) and before
+     * any poll calls that see the resulting status. The asynchronous
+     * call the event is paired with must ensure that any update to
+     * state that should be visible to the caller of wait* or poll
+     * happens before the call to IEvent::notify.
+     *
+     * IEvent::notify can be called at most once on a given event.
+     *
+     * @param neuralnetworks::V1_0::Status SUCCESS or ERROR
+     */
+    Return<void> notify(ReturnedStatus status) override;
+
+    /**
+     * Event::poll returns the current status of the event.
+     *
+     * @return Status SUCCESS, ERROR, or WAITING
+     */
+    Event::Status poll();
+
+    /**
+     * Event::wait blocks until the event has been signaled.
+     *
+     * @return Status SUCCESS or ERROR
+     */
+    Event::Status wait();
+
+    /**
+     * Event::wait_for blocks until the event has been signaled or the time
+     * duration from the time the wait_for function was called has expired,
+     * whichever comes first.
+     *
+     * @return Status SUCCESS, ERROR, or TIMEOUT
+     */
+    template<class Rep, class Period>
+    Event::Status wait_for(const std::chrono::duration<Rep,Period>& timeout_duration);
+
+    /**
+     * Event::wait_until blocks until the event has been signaled or a certain
+     * time has been reached, whichever comes first.
+     *
+     * @return Status SUCCESS, ERROR, or TIMEOUT
+     */
+    template<class Clock, class Duration>
+    Event::Status wait_until(const std::chrono::time_point<Clock,Duration>& timeout_duration);
+
+    /**
+     * Event::on_finish binds a callback function to the event. The
+     * callback will be executed when IEvent::notify is called, before
+     * any calls to wait* return. (Note that wait_for or wait_until
+     * can return TIMEOUT before IEvent::notify is called for the
+     * first time, and hence before the callback is executed.)
+     *
+     * The callback function must not synchronize with or otherwise
+     * access the event object it is bound to.
+     *
+     * Event::on_finish can be called at most once on a given event.
+     *
+     * @param callback Function to be invoked the first time IEvent::notify is
+     *                 called. Must have a target -- i.e., must not compare equal
+     *                 to nullptr. Callback returns true if it successfully
+     *                 completes, false if it fails.
+     * @return bool True if the callback was successfully bound, false if
+     *              unsuccessful.
+     *
+     * TODO: What if notify has already been called before on_finish?
+     * TODO: Why does the return value of the callback matter?
+     */
+     bool on_finish(std::function<bool(void)> callback);
+
+    /**
+     * Event::bind_thread binds a thread to the event ensuring that the thread
+     * has fully finished and cleaned its resources before the event is
+     * destroyed. The thread should be bound using std::move.
+     *
+     * The bound thread shall not call any Event method with the exception of
+     * IEvent::notify, which it will call when the thread has finished its
+     * computation.
+     *
+     * Event::bind_thread can be called at most once on a given event.
+     *
+     * @param asyncThread Thread to be bound to the event. The thread object
+     *                    must represent a thread of execution -- i.e.,
+     *                    asyncThread.joinable() must be true.
+     * @return bool True if successful, false if thread was not properly bound.
+     */
+     bool bind_thread(std::thread&& asyncThread);
+
+ private:
+    Status                    mStatus;
+    std::mutex                mMutex;
+    std::condition_variable   mCondition;
+    std::function<bool(void)> mCallback;
+    std::thread               mThread;
+};
+
+
+// template function implementations
+
+template<class Rep, class Period>
+Event::Status Event::wait_for(const std::chrono::duration<Rep,Period>& timeout_duration) {
+    std::unique_lock<std::mutex> lock(mMutex);
+    std::cv_status status = mCondition.wait_for(lock, timeout_duration,
+                                                [this]{return mStatus != Status::WAITING;});
+    return status != std::cv_status::timeout ? mStatus : Status::TIMEOUT;
+}
+
+template<class Clock, class Duration>
+Event::Status Event::wait_until(const std::chrono::time_point<Clock,Duration>& timeout_time) {
+    std::unique_lock<std::mutex> lock(mMutex);
+    std::cv_status status = mCondition.wait_until(lock, timeout_time,
+                                                  [this]{return mStatus != Status::WAITING;});
+    return status != std::cv_status::timeout ? mStatus : Status::TIMEOUT;
+}
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace neuralnetworks
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_NEURALNETWORKS_V1_0_EVENT_H
diff --git a/neuralnetworks/1.0/vts/functional/VtsHalNeuralnetworksV1_0TargetTest.cpp b/neuralnetworks/1.0/vts/functional/VtsHalNeuralnetworksV1_0TargetTest.cpp
index 84e67cb..6655ad3 100644
--- a/neuralnetworks/1.0/vts/functional/VtsHalNeuralnetworksV1_0TargetTest.cpp
+++ b/neuralnetworks/1.0/vts/functional/VtsHalNeuralnetworksV1_0TargetTest.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "neuralnetworks_hidl_hal_test"
 
+#include "Event.h"
 #include "VtsHalNeuralnetworksV1_0TargetTest.h"
 #include <android-base/logging.h>
 #include <android/hidl/memory/1.0/IMemory.h>
@@ -29,9 +30,13 @@
 namespace vts {
 namespace functional {
 
+using ::android::hardware::neuralnetworks::V1_0::implementation::Event;
+
 // A class for test environment setup
 NeuralnetworksHidlEnvironment::NeuralnetworksHidlEnvironment() {}
 
+NeuralnetworksHidlEnvironment::~NeuralnetworksHidlEnvironment() {}
+
 NeuralnetworksHidlEnvironment* NeuralnetworksHidlEnvironment::getInstance() {
     // This has to return a "new" object because it is freed inside
     // ::testing::AddGlobalTestEnvironment when the gtest is being torn down
@@ -44,6 +49,8 @@
 }
 
 // The main test class for NEURALNETWORK HIDL HAL.
+NeuralnetworksHidlTest::~NeuralnetworksHidlTest() {}
+
 void NeuralnetworksHidlTest::SetUp() {
     device = ::testing::VtsHalHidlTargetTestBase::getService<IDevice>(
         NeuralnetworksHidlEnvironment::getInstance());
@@ -226,21 +233,32 @@
     float* outputPtr = reinterpret_cast<float*>(static_cast<void*>(outputMemory->getPointer()));
     ASSERT_NE(nullptr, inputPtr);
     ASSERT_NE(nullptr, outputPtr);
+    inputMemory->update();
+    outputMemory->update();
     std::copy(inputData.begin(), inputData.end(), inputPtr);
     std::copy(outputData.begin(), outputData.end(), outputPtr);
     inputMemory->commit();
     outputMemory->commit();
 
     // execute request
-    bool success = preparedModel->execute({.inputs = inputs, .outputs = outputs, .pools = pools});
+    sp<Event> event = sp<Event>(new Event());
+    ASSERT_NE(nullptr, event.get());
+    bool success = preparedModel->execute({.inputs = inputs, .outputs = outputs, .pools = pools},
+                                          event);
     EXPECT_TRUE(success);
+    Event::Status status = event->wait();
+    EXPECT_EQ(Event::Status::SUCCESS, status);
 
     // validate results { 1+5, 2+6, 3+7, 4+8 }
-    outputMemory->update();
+    outputMemory->read();
     std::copy(outputPtr, outputPtr + outputData.size(), outputData.begin());
+    outputMemory->commit();
     EXPECT_EQ(expectedData, outputData);
 }
 
+// TODO: Add tests for execution failure, or wait_for/wait_until timeout.
+//       Discussion: https://googleplex-android-review.git.corp.google.com/#/c/platform/hardware/interfaces/+/2654636/5/neuralnetworks/1.0/vts/functional/VtsHalNeuralnetworksV1_0TargetTest.cpp@222
+
 }  // namespace functional
 }  // namespace vts
 }  // namespace V1_0
diff --git a/neuralnetworks/1.0/vts/functional/VtsHalNeuralnetworksV1_0TargetTest.h b/neuralnetworks/1.0/vts/functional/VtsHalNeuralnetworksV1_0TargetTest.h
index bb0cdaa..fdd3b0b 100644
--- a/neuralnetworks/1.0/vts/functional/VtsHalNeuralnetworksV1_0TargetTest.h
+++ b/neuralnetworks/1.0/vts/functional/VtsHalNeuralnetworksV1_0TargetTest.h
@@ -59,15 +59,17 @@
     NeuralnetworksHidlEnvironment& operator=(NeuralnetworksHidlEnvironment&&) = delete;
 
    public:
+    ~NeuralnetworksHidlEnvironment() override;
     static NeuralnetworksHidlEnvironment* getInstance();
-    virtual void registerTestServices() override;
+    void registerTestServices() override;
 };
 
 // The main test class for NEURALNETWORKS HIDL HAL.
 class NeuralnetworksHidlTest : public ::testing::VtsHalHidlTargetTestBase {
    public:
-    virtual void SetUp() override;
-    virtual void TearDown() override;
+    ~NeuralnetworksHidlTest() override;
+    void SetUp() override;
+    void TearDown() override;
 
     sp<IDevice> device;
 };