From 23d0e562e0013469cc1aacec4c221db406ce8789 Mon Sep 17 00:00:00 2001 From: Michael Butler Date: Tue, 16 Jul 2019 16:52:06 -0700 Subject: [PATCH] Update NN VTS callback objects The VTS Callback files are a subset of the Callback files in frameworks/ml/nn/runtime/Callbacks.*. This CL syncs the implementations, removing the functionality that is not needed in VTS. Fixes: 132322149 Test: mma Test: VtsHalNeuralnetworksV1_0TargetTest Test: VtsHalNeuralnetworksV1_1TargetTest Test: VtsHalNeuralnetworksV1_2TargetTest Change-Id: I114ce7f3b6c3d58de0196e9508209614d0a73e11 --- .../1.0/vts/functional/Callbacks.cpp | 150 ++---- .../vts/functional/include/1.0/Callbacks.h | 337 ++++-------- .../1.2/vts/functional/Callbacks.cpp | 188 +++---- .../functional/CompilationCachingTests.cpp | 1 + .../vts/functional/include/1.2/Callbacks.h | 489 ++++++++---------- 5 files changed, 442 insertions(+), 723 deletions(-) diff --git a/neuralnetworks/1.0/vts/functional/Callbacks.cpp b/neuralnetworks/1.0/vts/functional/Callbacks.cpp index 2b5723dc3d..37afcf0bd9 100644 --- a/neuralnetworks/1.0/vts/functional/Callbacks.cpp +++ b/neuralnetworks/1.0/vts/functional/Callbacks.cpp @@ -14,130 +14,78 @@ * limitations under the License. */ +#define LOG_TAG "Callbacks" + #include "1.0/Callbacks.h" + #include -namespace android { -namespace hardware { -namespace neuralnetworks { -namespace V1_0 { -namespace implementation { +namespace android::hardware::neuralnetworks::V1_0::implementation { -CallbackBase::CallbackBase() : mNotified(false) {} - -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 -} - -void CallbackBase::wait() { - std::unique_lock lock(mMutex); - mCondition.wait(lock, [this]{return mNotified;}); - join_thread_locked(); -} - -bool CallbackBase::on_finish(std::function post_work) { - std::lock_guard 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 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::join_thread() { - std::lock_guard lock(mMutex); - join_thread_locked(); -} - -void CallbackBase::notify() { - { - std::lock_guard lock(mMutex); - mNotified = true; - if (mPostWork != nullptr) { - bool success = mPostWork(); - if (!success) { - LOG(ERROR) << "CallbackBase::notify -- post work failed"; - } - } - } - mCondition.notify_all(); -} - -void CallbackBase::join_thread_locked() { - if (mThread.joinable()) { - mThread.join(); - } -} - -PreparedModelCallback::PreparedModelCallback() : - mErrorStatus(ErrorStatus::GENERAL_FAILURE), mPreparedModel(nullptr) {} - -PreparedModelCallback::~PreparedModelCallback() {} +// PreparedModelCallback methods begin here Return PreparedModelCallback::notify(ErrorStatus errorStatus, - const sp& preparedModel) { - mErrorStatus = errorStatus; - mPreparedModel = preparedModel; - CallbackBase::notify(); + const sp& preparedModel) { + { + std::lock_guard hold(mMutex); + + // quick-return if object has already been notified + if (mNotified) { + return Void(); + } + + // store results and mark as notified + mErrorStatus = errorStatus; + mPreparedModel = preparedModel; + mNotified = true; + } + + mCondition.notify_all(); return Void(); } -ErrorStatus PreparedModelCallback::getStatus() { +void PreparedModelCallback::wait() const { + std::unique_lock lock(mMutex); + mCondition.wait(lock, [this] { return mNotified; }); +} + +ErrorStatus PreparedModelCallback::getStatus() const { wait(); return mErrorStatus; } -sp PreparedModelCallback::getPreparedModel() { +sp PreparedModelCallback::getPreparedModel() const { wait(); return mPreparedModel; } -ExecutionCallback::ExecutionCallback() : mErrorStatus(ErrorStatus::GENERAL_FAILURE) {} - -ExecutionCallback::~ExecutionCallback() {} +// ExecutionCallback methods begin here Return ExecutionCallback::notify(ErrorStatus errorStatus) { - mErrorStatus = errorStatus; - CallbackBase::notify(); + { + std::lock_guard hold(mMutex); + + // quick-return if object has already been notified + if (mNotified) { + return Void(); + } + + mErrorStatus = errorStatus; + mNotified = true; + } + mCondition.notify_all(); + return Void(); } -ErrorStatus ExecutionCallback::getStatus() { +void ExecutionCallback::wait() const { + std::unique_lock lock(mMutex); + mCondition.wait(lock, [this] { return mNotified; }); +} + +ErrorStatus ExecutionCallback::getStatus() const { wait(); return mErrorStatus; } -} // namespace implementation -} // namespace V1_0 -} // namespace neuralnetworks -} // namespace hardware -} // namespace android +} // namespace android::hardware::neuralnetworks::V1_0::implementation diff --git a/neuralnetworks/1.0/vts/functional/include/1.0/Callbacks.h b/neuralnetworks/1.0/vts/functional/include/1.0/Callbacks.h index 36318ea2c2..820bb107f8 100644 --- a/neuralnetworks/1.0/vts/functional/include/1.0/Callbacks.h +++ b/neuralnetworks/1.0/vts/functional/include/1.0/Callbacks.h @@ -17,268 +17,160 @@ #ifndef ANDROID_HARDWARE_NEURALNETWORKS_V1_0_CALLBACKS_H #define ANDROID_HARDWARE_NEURALNETWORKS_V1_0_CALLBACKS_H +#include #include #include #include -#include #include -#include #include -#include -namespace android { -namespace hardware { -namespace neuralnetworks { -namespace V1_0 { -namespace implementation { - -/** - * The CallbackBase class is used internally by the NeuralNetworks runtime to +/* + * The Callback classes are used internally by the NeuralNetworks runtime to * synchronize between different threads. An asynchronous task is launched * paired with a callback object. When a client thread requires the output being * generated 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. Any wait* may safely be called concurrently, even on the same - * callback object. When the asynchronous task has finished its workload, it - * must immediately call "notify". If the asynchronous task has failed to launch, - * the function that tried to launch the asynchronous task must immediately call - * "notify". This "notify" call awakens any client threads waiting on the - * callback object. + * and be blocked until it has completed. Any wait may safely be called + * concurrently, even on the same callback object. When the asynchronous task + * has finished its workload, it must immediately call "notify". If the + * asynchronous task has failed to launch, the function that tried to launch the + * asynchronous task must immediately call "notify". This "notify" call + * awakens any client threads waiting on the callback object. * - * The CallbackBase class implements some of the base synchronization common to - * both PrepareModelCallback and ExecutionCallback. For consistency, any HIDL - * callback class must inherit from CallbackBase as well as the HIDL callback - * interface it implements. - * - * 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. + * These classes exist 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. */ -class CallbackBase { - public: - CallbackBase(); - ~CallbackBase(); - /** - * CallbackBase::wait blocks until notify has been called on the callback - * object. - */ - void wait(); - - /** - * CallbackBase::wait_for blocks until notify has been called on the - * callback object or the time duration from the time the wait_for function - * was called has expired, whichever comes first. - * - * @return Status std::cv_status::no_timeout if the callback was notified - * before the time duration expired, std::cv_status::timeout - * otherwise. - */ - template - std::cv_status wait_for(const std::chrono::duration& timeout_duration); - - /** - * CallbackBase::on_finish binds a function to the callback object. This - * bound function will be executed when CallbackBase::notify is called, - * before any calls to wait* return. (Note that CallbackBase::wait_for can - * return std::cv_status::timeout before CallbackBase::notify is called for - * the first time, and hence before the bound function is executed.) - * - * The bound function must not synchronize with or otherwise access the - * callback object it is bound to, as this could cause a deadlock. - * - * CallbackBase::on_finish can be called at most once on a given callback - * object, and the call to CallbackBase::on_finish must finish before - * CallbackBase::notify is called. - * - * @param post_work Function to be invoked the first time - * CallbackBase::notify is called. Must have a target -- - * i.e., must not compare equal to nullptr. post_work - * returns true if it successfully completes, false if it - * fails. - * @return bool True if the function was successfully bound, false if - * unsuccessful. - * - * TODO: Why does the return value of the callback matter? - */ - bool on_finish(std::function post_work); - - /** - * CallbackBase::bind_thread binds a thread to the event for later use by - * CallbackBase::join_thread. - * - * The thread must be passed using std::move. - * - * Once a thread is bound with CallbackBase::bind_thread, the client code - * should ensure that one of the following occurs before the event is - * destroyed: - * - CallbackBase::join_thread has been called. - * - CallbackBase::wait has been called. - * - CallbackBase::wait_for has been called and returned other than - * std::cv_status::no_timeout. - * - * The bound thread shall not call any CallbackBase method with the - * exception of CallbackBase::notify, which it must call when the thread has - * finished its computation. - * - * CallbackBase::bind_thread can be called at most once on a given callback - * object. - * - * @param asyncThread Thread to be bound to the callback object. 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); - - /** - * CallbackBase::join_thread ensures that the thread (if any) bound to this - * event with CallbackBase::bind_thread has fully finished and cleaned its - * resources. It is legal to call this function multiple times, concurrently - * or sequentially. - */ - void join_thread(); - - protected: - /** - * CallbackBase::notify enables all prior and future wait* calls on the - * callback object to proceed. The call to CallbackBase::notify happens - * before any wait* calls on this callback object return (except in the case - * of wait_for timing out). The asynchronous call the callback object is - * paired with must ensure that any update to state that should be visible - * to the caller of wait* happens before the call to CallbackBase::notify. - * - * CallbackBase::notify must be called exactly once on a given callback - * object. - */ - void notify(); - - private: - // Same as CallbackBase::join_thread but assumes we already hold a lock on - // mMutex. - void join_thread_locked(); - - bool mNotified; - std::mutex mMutex; - std::condition_variable mCondition; - std::function mPostWork; - std::thread mThread; -}; +namespace android::hardware::neuralnetworks::V1_0::implementation { /** * The PreparedModelCallback class is used to receive the error status of * preparing a model as well as the prepared model from a task executing - * asynchronously with respect to the runtime. If a calling thread calls wait* + * asynchronously with respect to the runtime. If a calling thread calls wait * or get* on a PreparedModelCallback object and the corresponding asynchronous * task has not finished preparing the model, the calling thread will block - * until the asynchronous task has called notify. For more information on the - * synchronization behavior, refer to the CallbackBase class. + * until the asynchronous task has called notify. * - * This class inherits the basic blocking and signaling calls from - * CallbackBase, and implements the HIDL notify call from - * IPreparedModelCallback. This callback object is passed as an argument to - * IDevice::prepareModel. + * If the callback object is notified more than once, only the results of the + * first call to notify are used, and the results from subsequent calls are + * discarded. + * + * This callback object is passed as an argument to IDevice::prepareModel*. */ -class PreparedModelCallback : public CallbackBase, public IPreparedModelCallback { +class PreparedModelCallback : public IPreparedModelCallback { public: - PreparedModelCallback(); - ~PreparedModelCallback() override; - /** * IPreparedModelCallback::notify marks the callback object with the return * status of the asynchronous model preparation along with the prepared - * model and calls CallbackBase::notify, enabling all prior and future - * wait* calls on the PreparedModelCallback object to proceed. - * For more information on the synchronization behavior, refer to the - * CallbackBase class. + * model, and allows all prior and future wait calls on the + * PreparedModelCallback object to proceed. * - * IPreparedModelCallback::notify must be called exactly once on a given + * IPreparedModelCallback::notify must be called on a given * PreparedModelCallback object. * + * If the callback object is notified more than once, only the results of + * the first call to notify are used, and the results from subsequent calls + * are discarded. + * * @param status Error status returned from asynchronously preparing the - * model; will be: - * - NONE if the asynchronous preparation was successful - * - DEVICE_UNAVAILABLE if driver is offline or busy - * - GENERAL_FAILURE if there is an unspecified error - * - INVALID_ARGUMENT if the input model is invalid + * model; will be: + * - NONE if the asynchronous preparation was successful + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + * - INVALID_ARGUMENT if the input model is invalid * @param preparedModel Returned model that has been prepared for execution, - * nullptr if the model was unable to be prepared. + * nullptr if the model was unable to be prepared. */ - Return notify(ErrorStatus status, const sp& preparedModel) override; + Return notify(ErrorStatus status, const sp& preparedModel) override; + + /** + * PreparedModelCallback::wait blocks until notify has been called on the + * callback object. + */ + void wait() const; /** * Retrieves the error status returned from the asynchronous task launched - * by IDevice::prepareModel. If IDevice::prepareModel has not finished + * by IDevice::prepareModel*. If IDevice::prepareModel* has not finished * asynchronously preparing the model, this call will block until the * asynchronous task notifies the object. * * @return status Error status returned from asynchronously preparing the - * model; will be: - * - NONE if the asynchronous preparation was successful - * - DEVICE_UNAVAILABLE if driver is offline or busy - * - GENERAL_FAILURE if there is an unspecified error - * - INVALID_ARGUMENT if the input model is invalid + * model; will be: + * - NONE if the asynchronous preparation was successful + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + * - INVALID_ARGUMENT if the input model is invalid */ - ErrorStatus getStatus(); + ErrorStatus getStatus() const; /** * Retrieves the model that has been prepared for execution from the - * asynchronous task launched by IDevice::prepareModel. If - * IDevice::prepareModel has not finished asynchronously preparing the + * asynchronous task launched by IDevice::prepareModel*. If + * IDevice::prepareModel* has not finished asynchronously preparing the * model, this call will block until the asynchronous task notifies the * object. * * @return preparedModel Returned model that has been prepared for - * execution, nullptr if the model was unable to be - * prepared. + * execution, nullptr if the model was unable to be prepared. */ - sp getPreparedModel(); + sp getPreparedModel() const; private: - ErrorStatus mErrorStatus; - sp mPreparedModel; + mutable std::mutex mMutex; + mutable std::condition_variable mCondition; + bool mNotified GUARDED_BY(mMutex) = false; + ErrorStatus mErrorStatus = ErrorStatus::GENERAL_FAILURE; + sp mPreparedModel; }; /** - * The ExecutionCallback class is used to receive the error status of the - * execution from a task executing asynchronously with respect to the runtime. - * If a calling thread calls wait* or get* on a PreparedModelCallback object and - * the corresponding asynchronous task has not finished the execution, the - * calling thread will block until the asynchronous task has called notify. - * For more information on the synchronization behavior, refer to the - * CallbackBase class. + * The ExecutionCallback class is used to receive the results of the execution + * from a task executing asynchronously with respect to the runtime. If a + * calling thread calls wait or get* on a ExecutionCallback object and the + * corresponding asynchronous task has not finished the execution, the calling + * thread will block until the asynchronous task has called notify. * - * This class inherits the basic blocking and signaling calls from - * CallbackBase, and implements the HIDL notify call from IExecutionCallback. - * This callback object is passed as an argument to IPreparedModel::execute. + * If the callback object is notified more than once, only the results of the + * first call to notify are used, and the results from subsequent calls are + * discarded. + * + * This callback object is passed as an argument to IPreparedModel::execute*. */ -class ExecutionCallback : public CallbackBase, public IExecutionCallback { +class ExecutionCallback : public IExecutionCallback { public: - ExecutionCallback(); - ~ExecutionCallback() override; - /** * IExecutionCallback::notify marks the callback object with the return - * status of the asynchronous execution that held this callback and enable - * all prior and future wait* calls on the ExecutionCallback object to - * proceed. For more information on the synchronization behavior, refer to - * the CallbackBase class. + * status of the asynchronous execution that held this callback and enables + * all prior and future wait calls on the ExecutionCallback object to + * proceed. * - * IExecutionCallback::notify must be called exactly once on a given - * ExecutionCallback object. + * IExecutionCallback::notify must be called on a given ExecutionCallback + * object. + * + * If the callback object is notified more than once, only the results of + * the first call to notify are used, and the results from subsequent calls + * are discarded. * * @param status Error status returned from launching the asynchronous task - * (if the launch fails) or from the asynchronous task itself - * (if the launch succeeds). Must be: - * - NONE if the asynchronous execution was successful - * - DEVICE_UNAVAILABLE if driver is offline or busy - * - GENERAL_FAILURE if there is an unspecified error - * - OUTPUT_INSUFFICIENT_SIZE if provided output buffer is - * not large enough to store the resultant values - * - INVALID_ARGUMENT if the input request is invalid + * (if the launch fails) or from the asynchronous task itself (if the + * launch succeeds). Must be: + * - NONE if the asynchronous execution was successful + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + * - OUTPUT_INSUFFICIENT_SIZE if provided output buffer is not large + * enough to store the resultant values + * - INVALID_ARGUMENT if the input request is invalid */ Return notify(ErrorStatus status) override; + /** + * ExecutionCallback::wait blocks until notify has been called on the + * callback object. + */ + void wait() const; + /** * Retrieves the error status returned from the asynchronous task launched * by IPreparedModel::execute. If IPreparedModel::execute has not finished @@ -286,41 +178,26 @@ class ExecutionCallback : public CallbackBase, public IExecutionCallback { * task notifies the object. * * @return status Error status returned from launching the asynchronous task - * (if the launch fails) or from the asynchronous task itself - * (if the launch succeeds). Must be: - * - NONE if the asynchronous execution was successful - * - DEVICE_UNAVAILABLE if driver is offline or busy - * - GENERAL_FAILURE if the asynchronous task resulted in an - * unspecified error - * - OUTPUT_INSUFFICIENT_SIZE if at least one output - * operand buffer is not large enough to store the - * corresponding output - * - INVALID_ARGUMENT if one of the input arguments to - * prepareModel is invalid + * (if the launch fails) or from the asynchronous task itself (if the + * launch succeeds). Must be: + * - NONE if the asynchronous execution was successful + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if the asynchronous task resulted in an unspecified + * error + * - OUTPUT_INSUFFICIENT_SIZE if at least one output operand buffer is + * not large enough to store the corresponding output + * - INVALID_ARGUMENT if one of the input arguments to prepareModel is + * invalid */ - ErrorStatus getStatus(); + ErrorStatus getStatus() const; private: + mutable std::mutex mMutex; + mutable std::condition_variable mCondition; + bool mNotified GUARDED_BY(mMutex) = false; ErrorStatus mErrorStatus = ErrorStatus::GENERAL_FAILURE; }; -// template function implementation(s) below this point - -template -std::cv_status CallbackBase::wait_for(const std::chrono::duration& timeout_duration) { - std::unique_lock lock(mMutex); - std::cv_status status = - mCondition.wait_for(lock, timeout_duration, [this] { return mNotified; }); - if (status != std::cv_status::timeout) { - join_thread_locked(); - } - return status; -} - -} // namespace implementation -} // namespace V1_0 -} // namespace neuralnetworks -} // namespace hardware -} // namespace android +} // namespace android::hardware::neuralnetworks::V1_0::implementation #endif // ANDROID_HARDWARE_NEURALNETWORKS_V1_0_CALLBACKS_H diff --git a/neuralnetworks/1.2/vts/functional/Callbacks.cpp b/neuralnetworks/1.2/vts/functional/Callbacks.cpp index cfaf91d433..a607a083c0 100644 --- a/neuralnetworks/1.2/vts/functional/Callbacks.cpp +++ b/neuralnetworks/1.2/vts/functional/Callbacks.cpp @@ -14,160 +14,128 @@ * limitations under the License. */ +#define LOG_TAG "Callbacks" + #include "1.2/Callbacks.h" + #include -namespace android { -namespace hardware { -namespace neuralnetworks { -namespace V1_2 { -namespace implementation { +#include -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::max(), + .timeInDriver = std::numeric_limits::max()}; -void CallbackBase::wait() { - std::unique_lock lock(mMutex); - mCondition.wait(lock, [this] { return mNotified; }); - join_thread_locked(); -} - -bool CallbackBase::on_finish(std::function post_work) { - std::lock_guard 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 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::join_thread() { - std::lock_guard lock(mMutex); - join_thread_locked(); -} - -void CallbackBase::notify() { - { - std::lock_guard lock(mMutex); - mNotified = true; - if (mPostWork != nullptr) { - bool success = mPostWork(); - if (!success) { - LOG(ERROR) << "CallbackBase::notify -- post work failed"; - } - } - } - mCondition.notify_all(); -} - -void CallbackBase::join_thread_locked() { - if (mThread.joinable()) { - mThread.join(); - } -} - -PreparedModelCallback::PreparedModelCallback() - : mErrorStatus(ErrorStatus::GENERAL_FAILURE), mPreparedModel(nullptr) {} - -PreparedModelCallback::~PreparedModelCallback() {} +// PreparedModelCallback methods begin here Return PreparedModelCallback::notify(ErrorStatus errorStatus, const sp& preparedModel) { - mErrorStatus = errorStatus; - mPreparedModel = preparedModel; - CallbackBase::notify(); + { + std::lock_guard hold(mMutex); + + // quick-return if object has already been notified + if (mNotified) { + return Void(); + } + + // store results and mark as notified + mErrorStatus = errorStatus; + mPreparedModel = preparedModel; + mNotified = true; + } + + mCondition.notify_all(); return Void(); } Return PreparedModelCallback::notify_1_2(ErrorStatus errorStatus, const sp& preparedModel) { - mErrorStatus = errorStatus; - mPreparedModel = preparedModel; - CallbackBase::notify(); - return Void(); + return notify(errorStatus, preparedModel); } -ErrorStatus PreparedModelCallback::getStatus() { +void PreparedModelCallback::wait() const { + std::unique_lock lock(mMutex); + mCondition.wait(lock, [this] { return mNotified; }); +} + +ErrorStatus PreparedModelCallback::getStatus() const { wait(); return mErrorStatus; } -sp PreparedModelCallback::getPreparedModel() { +sp PreparedModelCallback::getPreparedModel() const { wait(); return mPreparedModel; } -ExecutionCallback::ExecutionCallback() : mErrorStatus(ErrorStatus::GENERAL_FAILURE) {} - -ExecutionCallback::~ExecutionCallback() {} +// ExecutionCallback methods begin here Return ExecutionCallback::notify(ErrorStatus errorStatus) { - mErrorStatus = errorStatus; - mOutputShapes = {}; - mTiming = {.timeOnDevice = UINT64_MAX, .timeInDriver = UINT64_MAX}; - CallbackBase::notify(); + notifyInternal(errorStatus, {}, kNoTiming); return Void(); } Return ExecutionCallback::notify_1_2(ErrorStatus errorStatus, const hidl_vec& outputShapes, const Timing& timing) { - mErrorStatus = errorStatus; - mOutputShapes = outputShapes; - mTiming = timing; - CallbackBase::notify(); + 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"; + notifyInternal(ErrorStatus::GENERAL_FAILURE, {}, kNoTiming); + return Void(); + } + } 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"; + notifyInternal(ErrorStatus::GENERAL_FAILURE, {}, kNoTiming); + return Void(); + } + } + notifyInternal(errorStatus, outputShapes, timing); return Void(); } -ErrorStatus ExecutionCallback::getStatus() { +void ExecutionCallback::wait() const { + std::unique_lock lock(mMutex); + mCondition.wait(lock, [this] { return mNotified; }); +} + +ErrorStatus ExecutionCallback::getStatus() const { wait(); return mErrorStatus; } -const std::vector& ExecutionCallback::getOutputShapes() { +const std::vector& 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 +void ExecutionCallback::notifyInternal(ErrorStatus errorStatus, + const hidl_vec& outputShapes, + const Timing& timing) { + { + std::lock_guard hold(mMutex); + + // quick-return if object has already been notified + if (mNotified) { + return; + } + + mErrorStatus = errorStatus; + mOutputShapes = outputShapes; + mTiming = timing; + mNotified = true; + } + mCondition.notify_all(); +} + +} // namespace android::hardware::neuralnetworks::V1_2::implementation diff --git a/neuralnetworks/1.2/vts/functional/CompilationCachingTests.cpp b/neuralnetworks/1.2/vts/functional/CompilationCachingTests.cpp index adbf224fee..ac92a5b0a2 100644 --- a/neuralnetworks/1.2/vts/functional/CompilationCachingTests.cpp +++ b/neuralnetworks/1.2/vts/functional/CompilationCachingTests.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "1.2/Callbacks.h" #include "GeneratedTestHarness.h" diff --git a/neuralnetworks/1.2/vts/functional/include/1.2/Callbacks.h b/neuralnetworks/1.2/vts/functional/include/1.2/Callbacks.h index 212a8875df..2992c0cfe9 100644 --- a/neuralnetworks/1.2/vts/functional/include/1.2/Callbacks.h +++ b/neuralnetworks/1.2/vts/functional/include/1.2/Callbacks.h @@ -17,299 +17,218 @@ #ifndef ANDROID_HARDWARE_NEURALNETWORKS_V1_2_CALLBACKS_H #define ANDROID_HARDWARE_NEURALNETWORKS_V1_2_CALLBACKS_H +#include +#include +#include #include #include #include -#include #include -#include #include -#include -namespace android { -namespace hardware { -namespace neuralnetworks { -namespace V1_2 { -namespace implementation { +/* + * The Callback classes are used internally by the NeuralNetworks runtime to + * synchronize between different threads. An asynchronous task is launched + * paired with a callback object. When a client thread requires the output being + * generated by the asynchronous task, the client thread can wait for the result + * and be blocked until it has completed. Any wait may safely be called + * concurrently, even on the same callback object. When the asynchronous task + * has finished its workload, it must immediately call "notify*". If the + * asynchronous task has failed to launch, the function that tried to launch the + * asynchronous task must immediately call "notify*". This "notify*" call + * awakens any client threads waiting on the callback object. + * + * These classes exist 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. + */ + +namespace android::hardware::neuralnetworks::V1_2::implementation { using V1_0::ErrorStatus; -/** - * The CallbackBase class is used internally by the NeuralNetworks runtime to - * synchronize between different threads. An asynchronous task is launched - * paired with a callback object. When a client thread requires the output being - * generated 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. Any wait* may safely be called concurrently, even on the same - * callback object. When the asynchronous task has finished its workload, it - * must immediately call "notify". If the asynchronous task has failed to launch, - * the function that tried to launch the asynchronous task must immediately call - * "notify". This "notify" call awakens any client threads waiting on the - * callback object. - * - * The CallbackBase class implements some of the base synchronization common to - * both PrepareModelCallback and ExecutionCallback. For consistency, any HIDL - * callback class must inherit from CallbackBase as well as the HIDL callback - * interface it implements. - * - * 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. - */ -class CallbackBase { - public: - CallbackBase(); - ~CallbackBase(); - - /** - * CallbackBase::wait blocks until notify has been called on the callback - * object. - */ - void wait(); - - /** - * CallbackBase::wait_for blocks until notify has been called on the - * callback object or the time duration from the time the wait_for function - * was called has expired, whichever comes first. - * - * @return Status std::cv_status::no_timeout if the callback was notified - * before the time duration expired, std::cv_status::timeout - * otherwise. - */ - template - std::cv_status wait_for(const std::chrono::duration& timeout_duration); - - /** - * CallbackBase::on_finish binds a function to the callback object. This - * bound function will be executed when CallbackBase::notify is called, - * before any calls to wait* return. (Note that CallbackBase::wait_for can - * return std::cv_status::timeout before CallbackBase::notify is called for - * the first time, and hence before the bound function is executed.) - * - * The bound function must not synchronize with or otherwise access the - * callback object it is bound to, as this could cause a deadlock. - * - * CallbackBase::on_finish can be called at most once on a given callback - * object, and the call to CallbackBase::on_finish must finish before - * CallbackBase::notify is called. - * - * @param post_work Function to be invoked the first time - * CallbackBase::notify is called. Must have a target -- - * i.e., must not compare equal to nullptr. post_work - * returns true if it successfully completes, false if it - * fails. - * @return bool True if the function was successfully bound, false if - * unsuccessful. - * - * TODO: Why does the return value of the callback matter? - */ - bool on_finish(std::function post_work); - - /** - * CallbackBase::bind_thread binds a thread to the event for later use by - * CallbackBase::join_thread. - * - * The thread must be passed using std::move. - * - * Once a thread is bound with CallbackBase::bind_thread, the client code - * should ensure that one of the following occurs before the event is - * destroyed: - * - CallbackBase::join_thread has been called. - * - CallbackBase::wait has been called. - * - CallbackBase::wait_for has been called and returned other than - * std::cv_status::no_timeout. - * - * The bound thread shall not call any CallbackBase method with the - * exception of CallbackBase::notify, which it must call when the thread has - * finished its computation. - * - * CallbackBase::bind_thread can be called at most once on a given callback - * object. - * - * @param asyncThread Thread to be bound to the callback object. 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); - - /** - * CallbackBase::join_thread ensures that the thread (if any) bound to this - * event with CallbackBase::bind_thread has fully finished and cleaned its - * resources. It is legal to call this function multiple times, concurrently - * or sequentially. - */ - void join_thread(); - - protected: - /** - * CallbackBase::notify enables all prior and future wait* calls on the - * callback object to proceed. The call to CallbackBase::notify happens - * before any wait* calls on this callback object return (except in the case - * of wait_for timing out). The asynchronous call the callback object is - * paired with must ensure that any update to state that should be visible - * to the caller of wait* happens before the call to CallbackBase::notify. - * - * CallbackBase::notify must be called exactly once on a given callback - * object. - */ - void notify(); - - private: - // Same as CallbackBase::join_thread but assumes we already hold a lock on - // mMutex. - void join_thread_locked(); - - bool mNotified; - std::mutex mMutex; - std::condition_variable mCondition; - std::function mPostWork; - std::thread mThread; -}; - /** * The PreparedModelCallback class is used to receive the error status of * preparing a model as well as the prepared model from a task executing - * asynchronously with respect to the runtime. If a calling thread calls wait* + * asynchronously with respect to the runtime. If a calling thread calls wait * or get* on a PreparedModelCallback object and the corresponding asynchronous * task has not finished preparing the model, the calling thread will block - * until the asynchronous task has either called notify or notify_1_2. For more - * information on the synchronization behavior, refer to the CallbackBase class. + * until the asynchronous task has either called notify or notify_1_2. * - * This class inherits the basic blocking and signaling calls from - * CallbackBase, and implements the HIDL notify and notify_1_2 calls from - * IPreparedModelCallback. This callback object is passed as an argument to - * IDevice::prepareModel. + * If the callback object is notified more than once, only the results of the + * first call to notify* are used, and the results from subsequent calls are + * discarded. + * + * This callback object is passed as an argument to IDevice::prepareModel*. */ -class PreparedModelCallback : public CallbackBase, public IPreparedModelCallback { +class PreparedModelCallback : public IPreparedModelCallback { public: - PreparedModelCallback(); - ~PreparedModelCallback() override; - /** - * IPreparedModelCallback::notify and IPreparedModelCallback::notify_1_2 - * mark the callback object with the return status of the asynchronous - * model preparation along with the prepared model, and call - * CallbackBase::notify, enabling all prior and future wait* calls on the - * PreparedModelCallback object to proceed. For more information on the - * synchronization behavior, refer to the CallbackBase class. + * IPreparedModelCallback::notify marks the callback object with the return + * status of the asynchronous model preparation along with the prepared + * model, and allows all prior and future wait calls on the + * PreparedModelCallback object to proceed. * - * Either IPreparedModelCallback::notify or IPreparedModelCallback::notify_1_2 - * must be called exactly once on a given PreparedModelCallback object. + * Either IPreparedModelCallback::notify or + * IPreparedModelCallback::notify_1_2 must be called on a given + * PreparedModelCallback object. + * + * If the callback object is notified more than once, only the results of + * the first call to notify* are used, and the results from subsequent calls + * are discarded. * * @param status Error status returned from asynchronously preparing the - * model; will be: - * - NONE if the asynchronous preparation was successful - * - DEVICE_UNAVAILABLE if driver is offline or busy - * - GENERAL_FAILURE if there is an unspecified error - * - INVALID_ARGUMENT if the input model is invalid + * model; will be: + * - NONE if the asynchronous preparation was successful + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + * - INVALID_ARGUMENT if the input model is invalid * @param preparedModel Returned model that has been prepared for execution, - * nullptr if the model was unable to be prepared. + * nullptr if the model was unable to be prepared. */ Return notify(ErrorStatus status, const sp& preparedModel) override; + + /** + * IPreparedModelCallback::notify_1_2 marks the callback object with the + * return status of the asynchronous model preparation along with the + * prepared model, and allows all prior and future wait calls on the + * PreparedModelCallback object to proceed. + * + * Either IPreparedModelCallback::notify or + * IPreparedModelCallback::notify_1_2 must be called on a given + * PreparedModelCallback object. + * + * If the callback object is notified more than once, only the results of + * the first call to notify* are used, and the results from subsequent calls + * are discarded. + * + * @param status Error status returned from asynchronously preparing the + * model; will be: + * - NONE if the asynchronous preparation was successful + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + * - INVALID_ARGUMENT if the input model is invalid + * @param preparedModel Returned model that has been prepared for execution, + * nullptr if the model was unable to be prepared. + */ Return notify_1_2(ErrorStatus status, const sp& preparedModel) override; + /** + * PreparedModelCallback::wait blocks until notify* has been called on the + * callback object. + */ + void wait() const; + /** * Retrieves the error status returned from the asynchronous task launched - * by IDevice::prepareModel. If IDevice::prepareModel has not finished + * by IDevice::prepareModel*. If IDevice::prepareModel* has not finished * asynchronously preparing the model, this call will block until the * asynchronous task notifies the object. * * @return status Error status returned from asynchronously preparing the - * model; will be: - * - NONE if the asynchronous preparation was successful - * - DEVICE_UNAVAILABLE if driver is offline or busy - * - GENERAL_FAILURE if there is an unspecified error - * - INVALID_ARGUMENT if the input model is invalid + * model; will be: + * - NONE if the asynchronous preparation was successful + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + * - INVALID_ARGUMENT if the input model is invalid */ - ErrorStatus getStatus(); + ErrorStatus getStatus() const; /** * Retrieves the model that has been prepared for execution from the - * asynchronous task launched by IDevice::prepareModel. If - * IDevice::prepareModel has not finished asynchronously preparing the + * asynchronous task launched by IDevice::prepareModel*. If + * IDevice::prepareModel* has not finished asynchronously preparing the * model, this call will block until the asynchronous task notifies the * object. * * @return preparedModel Returned model that has been prepared for - * execution, nullptr if the model was unable to be - * prepared. + * execution, nullptr if the model was unable to be prepared. */ - sp getPreparedModel(); + sp getPreparedModel() const; private: - ErrorStatus mErrorStatus; + mutable std::mutex mMutex; + mutable std::condition_variable mCondition; + bool mNotified GUARDED_BY(mMutex) = false; + ErrorStatus mErrorStatus = ErrorStatus::GENERAL_FAILURE; sp mPreparedModel; }; /** - * The ExecutionCallback class is used to receive the error status of the - * execution from a task executing asynchronously with respect to the runtime. - * If a calling thread calls wait* or get* on a PreparedModelCallback object and - * the corresponding asynchronous task has not finished the execution, the - * calling thread will block until the asynchronous task has either called notify - * or notify_1_2. For more information on the synchronization behavior, refer to - * the CallbackBase class. + * The ExecutionCallback class is used to receive the results of the execution + * from a task executing asynchronously with respect to the runtime. If a + * calling thread calls wait or get* on a ExecutionCallback object and the + * corresponding asynchronous task has not finished the execution, the calling + * thread will block until the asynchronous task has either called notify or + * notify_1_2. * - * This class inherits the basic blocking and signaling calls from - * CallbackBase, and implements the HIDL notify and notify_1_2 calls from - * IExecutionCallback. This callback object is passed as an argument to - * IPreparedModel::execute. + * If the callback object is notified more than once, only the results of the + * first call to notify* are used, and the results from subsequent calls are + * discarded. + * + * This callback object is passed as an argument to IPreparedModel::execute*. */ -class ExecutionCallback : public CallbackBase, public IExecutionCallback { +class ExecutionCallback : public IExecutionCallback { public: - ExecutionCallback(); - ~ExecutionCallback() override; - /** - * IExecutionCallback::notify and IExecutionCallback::notify_1_2 mark the - * callback object with the return status of the asynchronous execution that - * held this callback and enable all prior and future wait* calls on the - * ExecutionCallback object to proceed. For more information on the - * synchronization behavior, refer to the CallbackBase class. + * IExecutionCallback::notify marks the callback object with the return + * status of the asynchronous execution that held this callback and enables + * all prior and future wait calls on the ExecutionCallback object to + * proceed. * * Either IExecutionCallback::notify or IExecutionCallback::notify_1_2 must - * be called exactly once on a given ExecutionCallback object. + * be called on a given ExecutionCallback object. + * + * If the callback object is notified more than once, only the results of + * the first call to notify* are used, and the results from subsequent calls + * are discarded. * * @param status Error status returned from launching the asynchronous task - * (if the launch fails) or from the asynchronous task itself - * (if the launch succeeds). Must be: - * - NONE if the asynchronous execution was successful - * - DEVICE_UNAVAILABLE if driver is offline or busy - * - GENERAL_FAILURE if there is an unspecified error - * - OUTPUT_INSUFFICIENT_SIZE if provided output buffer is - * not large enough to store the resultant values - * - INVALID_ARGUMENT if the input request is invalid + * (if the launch fails) or from the asynchronous task itself (if the + * launch succeeds). Must be: + * - NONE if the asynchronous execution was successful + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + * - OUTPUT_INSUFFICIENT_SIZE if provided output buffer is not large + * enough to store the resultant values + * - INVALID_ARGUMENT if the input request is invalid */ Return notify(ErrorStatus status) override; /** - * Similar to IExecutionCallback::notify, but for V1_2::IPreparedModel to - * also notify output shapes along with error status. + * IExecutionCallback::notify_1_2 marks the callback object with the results + * (error status, dynamic output shapes, and timing information) of the + * asynchronous execution that held this callback and enables all prior and + * future wait calls on the ExecutionCallback object to proceed. + * + * Either IExecutionCallback::notify or IExecutionCallback::notify_1_2 must + * be called on a given ExecutionCallback object. + * + * If the callback object is notified more than once, only the results of + * the first call to notify* are used, and the results from subsequent calls + * are discarded. * * @param status Error status returned from launching the asynchronous task - * (if the launch fails) or from the asynchronous task itself - * (if the launch succeeds). Must be: - * - NONE if the asynchronous execution was successful - * - DEVICE_UNAVAILABLE if driver is offline or busy - * - GENERAL_FAILURE if the asynchronous task resulted in an - * unspecified error - * - OUTPUT_INSUFFICIENT_SIZE if at least one output - * operand buffer is not large enough to store the - * corresponding output - * - INVALID_ARGUMENT if one of the input arguments to - * prepareModel is invalid + * (if the launch fails) or from the asynchronous task itself (if the + * launch succeeds). Must be: + * - NONE if the asynchronous execution was successful + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if the asynchronous task resulted in an unspecified + * error + * - OUTPUT_INSUFFICIENT_SIZE if at least one output operand buffer is + * not large enough to store the corresponding output + * - INVALID_ARGUMENT if one of the input arguments to prepareModel is + * invalid * @param outputShapes A list of shape information of model output operands. - * The index into "outputShapes" corresponds to the index - * of the output operand in the Request outputs vector. - * outputShapes must be empty unless the status is either - * NONE or OUTPUT_INSUFFICIENT_SIZE. - * @return Timing Duration of execution. Unless MeasureTiming::YES was passed when - * launching the execution and status is NONE, all times must - * be reported as UINT64_MAX. A driver may choose to report - * any time as UINT64_MAX, indicating that particular measurement is - * not available. + * The index into "outputShapes" corresponds to the index of the output + * operand in the Request outputs vector. outputShapes must be empty + * unless the status is either NONE or OUTPUT_INSUFFICIENT_SIZE. + * @param Timing Duration of execution. Unless MeasureTiming::YES was passed + * when launching the execution and status is NONE, all times must be + * reported as UINT64_MAX. A driver may choose to report any time as + * UINT64_MAX, indicating that particular measurement is not available. */ Return notify_1_2(ErrorStatus status, const hidl_vec& outputShapes, const Timing& timing) override; @@ -320,82 +239,88 @@ class ExecutionCallback : public CallbackBase, public IExecutionCallback { return notify_1_2(status, outputShapes, timing); } + /** + * ExecutionCallback::wait blocks until notify* has been called on the + * callback object. + */ + void wait() const; + /** * Retrieves the error status returned from the asynchronous task launched * by either IPreparedModel::execute or IPreparedModel::execute_1_2. If * IPreparedModel::execute or IPreparedModel::execute_1_2 has not finished - * asynchronously executing, this call will block until the asynchronous task - * notifies the object. + * asynchronously executing, this call will block until the asynchronous + * task notifies the object. * * @return status Error status returned from launching the asynchronous task - * (if the launch fails) or from the asynchronous task itself - * (if the launch succeeds). Must be: - * - NONE if the asynchronous execution was successful - * - DEVICE_UNAVAILABLE if driver is offline or busy - * - GENERAL_FAILURE if the asynchronous task resulted in an - * unspecified error - * - OUTPUT_INSUFFICIENT_SIZE if at least one output - * operand buffer is not large enough to store the - * corresponding output - * - INVALID_ARGUMENT if one of the input arguments to - * prepareModel is invalid + * (if the launch fails) or from the asynchronous task itself (if the + * launch succeeds). Must be: + * - NONE if the asynchronous execution was successful + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if the asynchronous task resulted in an unspecified + * error + * - OUTPUT_INSUFFICIENT_SIZE if at least one output operand buffer is + * not large enough to store the corresponding output + * - INVALID_ARGUMENT if one of the input arguments to prepareModel is + * invalid */ - ErrorStatus getStatus(); + ErrorStatus getStatus() const; /** * Retrieves the output shapes returned from the asynchronous task launched - * by IPreparedModel::execute_1_2. If IPreparedModel::execute_1_2 has not finished - * asynchronously executing, this call will block until the asynchronous task - * notifies the object. + * by IPreparedModel::execute_1_2. If IPreparedModel::execute_1_2 has not + * finished asynchronously executing, this call will block until the + * asynchronous task notifies the object. * - * If the asynchronous task was launched by IPreparedModel::execute, an empty vector - * will be returned. + * If the asynchronous task was launched by IPreparedModel::execute, an + * empty vector will be returned. * - * @return outputShapes A list of shape information of model output operands. - * The index into "outputShapes" corresponds to the index - * of the output operand in the Request outputs vector. - * outputShapes must be empty unless the status is either - * NONE or OUTPUT_INSUFFICIENT_SIZE. + * @return outputShapes A list of shape information of model output + * operands. The index into "outputShapes" corresponds to the index of + * the output operand in the Request outputs vector. outputShapes must + * be empty unless the status is either NONE or + * OUTPUT_INSUFFICIENT_SIZE. outputShaps may be empty if the status is + * NONE and all model output operands are fully-specified at execution + * time. outputShapes must have the same number of elements as the + * number of model output operands if the status is + * OUTPUT_INSUFFICIENT_SIZE, or if the status is NONE and the model has + * at least one output operand that is not fully-specified. */ - const std::vector& getOutputShapes(); + const std::vector& getOutputShapes() const; /** - * Retrieves the duration of execution ofthe asynchronous task launched - * by IPreparedModel::execute_1_2. If IPreparedModel::execute_1_2 has not finished - * asynchronously executing, this call will block until the asynchronous task - * notifies the object. + * Retrieves the duration of execution of the asynchronous task launched by + * IPreparedModel::execute_1_2. If IPreparedModel::execute_1_2 has not + * finished asynchronously executing, this call will block until the + * asynchronous task notifies the object. * - * If the asynchronous task was launched by IPreparedModel::execute, every time - * must be UINT64_MAX. + * If the asynchronous task was launched by IPreparedModel::execute, every + * time must be UINT64_MAX. * - * @return timing Duration of the execution. Every time must be UINT64_MAX unless - * the status is NONE. + * @return timing Duration of the execution. Every time must be UINT64_MAX + * unless the status is NONE. */ - Timing getTiming(); + Timing getTiming() const; private: + /* + * ExecutionCallback::notifyInternal stores the results of the execution + * (status, output shapes, and timing information) in the ExecutionCallback + * object before any call to wait or get* return. It then enables all prior + * and future wait calls on the ExecutionCallback object to proceed. + */ + void notifyInternal(ErrorStatus errorStatus, const hidl_vec& outputShapes, + const Timing& timing); + + // members + mutable std::mutex mMutex; + mutable std::condition_variable mCondition; + bool mNotified GUARDED_BY(mMutex) = false; ErrorStatus mErrorStatus = ErrorStatus::GENERAL_FAILURE; std::vector mOutputShapes = {}; Timing mTiming = {}; }; -// template function implementation(s) below this point - -template -std::cv_status CallbackBase::wait_for(const std::chrono::duration& timeout_duration) { - std::unique_lock lock(mMutex); - std::cv_status status = - mCondition.wait_for(lock, timeout_duration, [this] { return mNotified; }); - if (status != std::cv_status::timeout) { - join_thread_locked(); - } - return status; -} - -} // namespace implementation -} // namespace V1_2 -} // namespace neuralnetworks -} // namespace hardware -} // namespace android +} // namespace android::hardware::neuralnetworks::V1_2::implementation #endif // ANDROID_HARDWARE_NEURALNETWORKS_V1_2_CALLBACKS_H