Simplify ExecutionCallback
This CL simplifies ExecutionCallback by (1) merging the functionality of
CallbackBase into ExecutionCallback, (2) restricts permission to methods
that are only used internally in ExecutionCallback, and (3) simplifies
some of the documentation.
Bug: 118624080
Test: mma
Test: atest NeuralNetworksTest_static
Test: atest VtsHalNeuralnetworksV1_0TargetTest (with sample-all)
Test: atest VtsHalNeuralnetworksV1_1TargetTest (with sample-all)
Test: atest VtsHalNeuralnetworksV1_2TargetTest (with sample-all)
Change-Id: I8e0cb6cec0e71350a742be0ec05fb95a288dd78d
Merged-In: I8e0cb6cec0e71350a742be0ec05fb95a288dd78d
(cherry picked from commit a55536bc9c00e8b4a85b721a001e19072e22a65d)
diff --git a/runtime/Callbacks.cpp b/runtime/Callbacks.cpp
index 0c58a46..660def7 100644
--- a/runtime/Callbacks.cpp
+++ b/runtime/Callbacks.cpp
@@ -15,80 +15,17 @@
*/
#include "Callbacks.h"
+
#include <android-base/logging.h>
-namespace android {
-namespace hardware {
-namespace neuralnetworks {
-namespace V1_2 {
-namespace implementation {
+#include <limits>
-CallbackBase::CallbackBase() : mNotified(false) {}
+namespace android::hardware::neuralnetworks::V1_2::implementation {
-CallbackBase::~CallbackBase() {
- // Note that we cannot call CallbackBase::join_thread from here:
- // CallbackBase is intended to be reference counted, and it is possible that
- // the reference count drops to zero in the bound thread, causing the
- // bound thread to call this destructor. If a thread tries to join
- // itself, it throws an exception, producing a message like the
- // following:
- //
- // terminating with uncaught exception of type std::__1::system_error:
- // thread::join failed: Resource deadlock would occur
-}
+constexpr Timing kNoTiming = {.timeOnDevice = std::numeric_limits<uint64_t>::max(),
+ .timeInDriver = std::numeric_limits<uint64_t>::max()};
-void CallbackBase::wait() {
- std::unique_lock<std::mutex> lock(mMutex);
- mCondition.wait(lock, [this]{return mNotified;});
- join_thread_locked();
-}
-
-bool CallbackBase::on_finish(std::function<void()> post_work) {
- std::lock_guard<std::mutex> lock(mMutex);
- if (mPostWork != nullptr) {
- LOG(ERROR) << "CallbackBase::on_finish -- a post-work function has already been bound to "
- "this callback object";
- return false;
- }
- if (post_work == nullptr) {
- LOG(ERROR) << "CallbackBase::on_finish -- the new post-work function is invalid";
- return false;
- }
- mPostWork = std::move(post_work);
- return true;
-}
-
-bool CallbackBase::bind_thread(std::thread&& asyncThread) {
- std::lock_guard<std::mutex> lock(mMutex);
- if (mThread.joinable()) {
- LOG(ERROR) << "CallbackBase::bind_thread -- a thread has already been bound to this "
- "callback object";
- return false;
- }
- if (!asyncThread.joinable()) {
- LOG(ERROR) << "CallbackBase::bind_thread -- the new thread is not joinable";
- return false;
- }
- mThread = std::move(asyncThread);
- return true;
-}
-
-void CallbackBase::notify() {
- {
- std::lock_guard<std::mutex> lock(mMutex);
- mNotified = true;
- if (mPostWork) {
- mPostWork();
- }
- }
- mCondition.notify_all();
-}
-
-void CallbackBase::join_thread_locked() {
- if (mThread.joinable()) {
- mThread.join();
- }
-}
+// PreparedModelCallback methods begin here
Return<void> PreparedModelCallback::notify(ErrorStatus errorStatus,
const sp<V1_0::IPreparedModel>& preparedModel) {
@@ -130,73 +67,141 @@
return mPreparedModel;
}
-ExecutionCallback::ExecutionCallback()
- : mErrorStatus(ErrorStatus::GENERAL_FAILURE), mOnFinish(nullptr) {
- on_finish([this] {
- if (mOnFinish != nullptr) {
- ErrorStatus status = mOnFinish(mErrorStatus, mOutputShapes);
- if (status != ErrorStatus::NONE) {
- mErrorStatus = status;
- }
- }
- });
-}
-
-ExecutionCallback::~ExecutionCallback() {}
+// ExecutionCallback methods begin here
Return<void> ExecutionCallback::notify(ErrorStatus errorStatus) {
- mErrorStatus = errorStatus;
- mOutputShapes = {};
- mTiming = {.timeOnDevice = UINT64_MAX, .timeInDriver = UINT64_MAX};
- CallbackBase::notify();
+ notifyInternal(errorStatus, {}, kNoTiming);
return Void();
}
Return<void> ExecutionCallback::notify_1_2(ErrorStatus errorStatus,
const hidl_vec<OutputShape>& outputShapes,
const Timing& timing) {
- mErrorStatus = errorStatus;
- mOutputShapes = outputShapes;
- mTiming = timing;
- if (mErrorStatus == ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) {
- // mOutputShapes must not be empty if OUTPUT_INSUFFICIENT_SIZE.
- if (mOutputShapes.size() == 0) {
+ if (errorStatus == ErrorStatus::OUTPUT_INSUFFICIENT_SIZE) {
+ // outputShapes must not be empty if OUTPUT_INSUFFICIENT_SIZE.
+ if (outputShapes.size() == 0) {
LOG(ERROR) << "Notified with empty output shape vector when OUTPUT_INSUFFICIENT_SIZE";
- mErrorStatus = ErrorStatus::GENERAL_FAILURE;
- mOutputShapes = {};
- mTiming = {.timeOnDevice = UINT64_MAX, .timeInDriver = UINT64_MAX};
+ notifyInternal(ErrorStatus::GENERAL_FAILURE, {}, kNoTiming);
+ return Void();
}
- } else if (mErrorStatus != ErrorStatus::NONE) {
- // mOutputShapes must be empty if mErrorStatus is neither NONE nor OUTPUT_INSUFFICIENT_SIZE.
- if (mOutputShapes.size() != 0) {
+ } else if (errorStatus != ErrorStatus::NONE) {
+ // outputShapes must be empty if errorStatus is neither NONE nor OUTPUT_INSUFFICIENT_SIZE.
+ if (outputShapes.size() != 0) {
LOG(ERROR) << "Notified with non-empty output shape vector when error status is "
"neither NONE nor OUTPUT_INSUFFICIENT_SIZE";
- mErrorStatus = ErrorStatus::GENERAL_FAILURE;
- mOutputShapes = {};
- mTiming = {.timeOnDevice = UINT64_MAX, .timeInDriver = UINT64_MAX};
+ notifyInternal(ErrorStatus::GENERAL_FAILURE, {}, kNoTiming);
+ return Void();
}
}
- CallbackBase::notify();
+ notifyInternal(errorStatus, outputShapes, timing);
return Void();
}
-ErrorStatus ExecutionCallback::getStatus() {
+void ExecutionCallback::wait() const {
+ std::unique_lock<std::mutex> lock(mMutex);
+ mCondition.wait(lock, [this] { return mNotified; });
+
+ /*
+ * Note that we cannot call std::thread::join from ExecutionCallback's
+ * destructor: ExecutionCallback is intended to be reference counted, and it
+ * is possible that the reference count drops to zero in the bound thread,
+ * causing the bound thread to call this destructor. If a thread tries to
+ * join itself, it throws an exception, producing a message like the
+ * following:
+ *
+ * terminating with uncaught exception of type std::__1::system_error:
+ * thread::join failed: Resource deadlock would occur
+ */
+ if (mThread.joinable()) {
+ mThread.join();
+ }
+}
+
+ErrorStatus ExecutionCallback::getStatus() const {
wait();
return mErrorStatus;
}
-const std::vector<OutputShape>& ExecutionCallback::getOutputShapes() {
+const std::vector<OutputShape>& ExecutionCallback::getOutputShapes() const {
wait();
return mOutputShapes;
}
-Timing ExecutionCallback::getTiming() {
+Timing ExecutionCallback::getTiming() const {
wait();
return mTiming;
}
-} // namespace implementation
-} // namespace V1_2
-} // namespace neuralnetworks
-} // namespace hardware
-} // namespace android
+bool ExecutionCallback::bindThread(std::thread asyncThread) {
+ std::lock_guard<std::mutex> lock(mMutex);
+
+ // Ensure ExecutionCallback object does not already have a thread bound
+ if (mThread.joinable()) {
+ LOG(ERROR) << "ExecutionCallback::bindThread -- a thread has already been bound to this "
+ "callback object";
+ return false;
+ }
+
+ // Ensure the new thread is valid
+ if (!asyncThread.joinable()) {
+ LOG(ERROR) << "ExecutionCallback::bindThread -- the new thread is not joinable";
+ return false;
+ }
+
+ mThread = std::move(asyncThread);
+ return true;
+}
+
+void ExecutionCallback::setOnFinish(const ExecutionFinish& finish) {
+ std::lock_guard<std::mutex> hold(mMutex);
+
+ // Ensure ExecutionCallback object does not already have a "finish" callback
+ if (mOnFinish != nullptr) {
+ LOG(ERROR) << "ExecutionCallback::setOnFinish -- object already has a \"finish\" callback";
+ return;
+ }
+
+ // Ensure new "finish" callback is valid
+ if (finish == nullptr) {
+ LOG(ERROR) << "ExecutionCallback::setOnFinish -- \"finish\" callback is invalid";
+ return;
+ }
+
+ // Essure ExecutionCallback object has not already been notified
+ if (mNotified) {
+ LOG(ERROR) << "ExecutionCallback::setOnFinish -- ExecutionCallback has already been "
+ "notified with results";
+ return;
+ }
+
+ mOnFinish = finish;
+}
+
+void ExecutionCallback::notifyInternal(ErrorStatus errorStatus,
+ const hidl_vec<OutputShape>& outputShapes,
+ const Timing& timing) {
+ {
+ std::lock_guard<std::mutex> hold(mMutex);
+
+ // quick-return if object has already been notified
+ if (mNotified) {
+ return;
+ }
+
+ mErrorStatus = errorStatus;
+ mOutputShapes = outputShapes;
+ mTiming = timing;
+ mNotified = true;
+
+ if (mOnFinish != nullptr) {
+ ErrorStatus status = mOnFinish(mErrorStatus, mOutputShapes);
+ mOnFinish = nullptr;
+ if (status != ErrorStatus::NONE) {
+ mErrorStatus = status;
+ }
+ }
+ }
+ mCondition.notify_all();
+}
+
+} // namespace android::hardware::neuralnetworks::V1_2::implementation