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/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