diff --git a/rtos/ConditionVariable.h b/rtos/ConditionVariable.h index 7bfd3ef..cb6e56b 100644 --- a/rtos/ConditionVariable.h +++ b/rtos/ConditionVariable.h @@ -24,6 +24,7 @@ #define CONDITIONVARIABLE_H #include +#include #include "rtos/mbed_rtos_types.h" #include "rtos/Mutex.h" #include "rtos/Semaphore.h" @@ -36,6 +37,11 @@ /** \addtogroup rtos-public-api */ /** @{*/ +enum class cv_status { + no_timeout, + timeout +}; + struct Waiter; /** * \defgroup rtos_ConditionVariable ConditionVariable class @@ -178,7 +184,7 @@ * should check to make sure the condition the caller is waiting on has * been met. * - * @note - The current thread releases the lock while inside the wait + * @note - The current thread releases the mutex while inside the wait * function and reacquires it upon exiting the function. * * Example: @@ -198,6 +204,43 @@ */ void wait(); + /** Wait for a predicate. + * + * Wait causes the current thread to block until the predicate is + * true. + * + * @param pred A function-like object such that `pred()` is convertible to bool + * + * @note - The thread calling this function must be the owner of the + * ConditionVariable's mutex, and it must be locked exactly once. + * + * @note - The current thread releases the mutex while inside the wait + * function and reacquires it upon exiting the function. + * + * Example: + * @code + * extern bool data_available(); + * + * mutex.lock(); + * + * cond.wait(data_available); + * + * function_to_handle_data(); + * + * mutex.unlock(); + * @endcode + * + * @note You cannot call this function from ISR context. + */ + template + void wait(Predicate pred) + { + while (!pred()) { + wait(); + } + } + + /** Wait for a notification until the specified time. * * Wait until causes the current thread to block until the condition @@ -236,9 +279,94 @@ * @endcode * * @note You cannot call this function from ISR context. + * @deprecated Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` + * rather than `Kernel::get_ms_count() + 5000`. */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` rather than `Kernel::get_ms_count() + 5000`.") bool wait_until(uint64_t millisec); + /** Wait for a notification until the specified time. + * + * Wait until causes the current thread to block until the condition + * variable is notified, or a specific time given by millisec parameter is + * reached. + * + * @param abs_time Absolute end time referenced to `Kernel::Clock` + * @return `cv_status::timeout` if a timeout occurred, `cv_status::no_timeout` otherwise. + * + * @note - The thread calling this function must be the owner of the + * ConditionVariable's mutex, and it must be locked exactly once. + * + * @note - Spurious notifications can occur, so the caller of this API + * should check to make sure the condition the caller is waiting on has + * been met. + * + * @note - The current thread releases the lock while inside the wait + * function and reacquires it upon exiting the function. + * + * Example: + * @code + * mutex.lock(); + * Kernel::Clock::time_point end_time = Kernel::Clock::now() + 2s; + * + * while (!condition_met) { + * if (cond.wait_until(end_time) == cv_status::timeout) { + * break; + * } + * } + * + * if (condition_met) { + * function_to_handle_condition(); + * } + * + * mutex.unlock(); + * @endcode + * + * @note You cannot call this function from ISR context. + */ + cv_status wait_until(Kernel::Clock::time_point abs_time); + + /** Wait for a predicate until the specified time. + * + * Wait until causes the current thread to block until the predicate is true, + * or a specific time given by abs_time parameter is reached. + * + * @param abs_time Absolute end time referenced to `Kernel::Clock` + * @param pred A function-like object such that `pred()` is convertible to bool + * @return The state of the predicate + * + * @note - The thread calling this function must be the owner of the + * ConditionVariable's mutex, and it must be locked exactly once. + * + * @note - The current thread releases the mutex while inside the wait + * function and reacquires it upon exiting the function. + * + * Example: + * @code + * extern bool data_available(); + * + * mutex.lock(); + * + * if (cond.wait_until(Kernel::Clock::now() + 2s, data_available)) { + * function_to_handle_data(); + * } + * + * mutex.unlock(); + * @endcode + * + * @note You cannot call this function from ISR context. + */ + template + bool wait_until(Kernel::Clock::time_point abs_time, Predicate pred) + { + while (!pred()) { + if (wait_until(abs_time) == cv_status::timeout) { + return pred(); + } + } + return true; + } + /** Wait for a notification or timeout. * * `Wait for` causes the current thread to block until the condition @@ -277,9 +405,88 @@ * @endcode * * @note You cannot call this function from ISR context. + * @deprecated Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`. */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") bool wait_for(uint32_t millisec); + /** Wait for a notification or timeout. + * + * `Wait for` causes the current thread to block until the condition + * variable receives a notification from another thread, or the timeout + * specified by the millisec parameter is reached. + * + * @param rel_time Timeout value. + * @return `cv_status::timeout` if a timeout occurred, `cv_status::no_timeout` otherwise. + * + * @note - The thread calling this function must be the owner of the + * ConditionVariable's mutex, and it must be locked exactly once. + * + * @note - Spurious notifications can occur, so the caller of this API + * should check to make sure the condition the caller is waiting on has + * been met. + * + * @note - The current thread releases the lock while inside the wait + * function and reacquire it upon exiting the function. + * + * Example: + * @code + * mutex.lock(); + * + * while (!condition_met) { + * cond.wait_for(MAX_SLEEP_TIME); + * if (!condition_met) { + * do_other_work_while_condition_false(); + * } + * } + * + * if (condition_met) { + * function_to_handle_condition(); + * } + * + * mutex.unlock(); + * @endcode + * + * @note You cannot call this function from ISR context. + */ + cv_status wait_for(Kernel::Clock::duration_u32 rel_time); + + /** Wait for a predicate or timeout. + * + * `Wait for` causes the current thread to block until the predicate + * is true, or the timeout specified by the rel_time parameter is reached. + * + * @param rel_time Timeout value. + * @param pred a function-like object such that `pred()` is convertible to bool + * @return The state of the predicate + * + * @note - The thread calling this function must be the owner of the + * ConditionVariable's mutex, and it must be locked exactly once. + * + * @note - The current thread releases the mutex while inside the wait + * function and reacquire it upon exiting the function. + * + * Example: + * @code + * extern bool data_available(); + * + * mutex.lock(); + * + * if (cond.wait_for(2s, data_available)) { + * function_to_handle_data(); + * } + * + * mutex.unlock(); + * @endcode + * + * @note You cannot call this function from ISR context. + */ + template + bool wait_for(Kernel::Clock::duration rel_time, Predicate pred) + { + return wait_until(Kernel::Clock::now() + rel_time, std::move(pred)); + } + /** Notify one waiter on this condition variable that a condition changed. * * This function unblocks one of the threads waiting for the condition diff --git a/rtos/source/ConditionVariable.cpp b/rtos/source/ConditionVariable.cpp index 36cf9b0..d157114 100644 --- a/rtos/source/ConditionVariable.cpp +++ b/rtos/source/ConditionVariable.cpp @@ -29,6 +29,9 @@ #if MBED_CONF_RTOS_PRESENT +using std::chrono::duration; +using std::milli; + namespace rtos { ConditionVariable::Waiter::Waiter(): sem(0), prev(nullptr), next(nullptr), in_list(false) @@ -43,11 +46,16 @@ void ConditionVariable::wait() { - wait_for(osWaitForever); + wait_for(Kernel::wait_for_u32_forever); } bool ConditionVariable::wait_for(uint32_t millisec) { + return wait_for(duration(millisec)) == cv_status::timeout; +} + +cv_status ConditionVariable::wait_for(Kernel::Clock::duration_u32 rel_time) +{ Waiter current_thread; MBED_ASSERT(_mutex.get_owner() == ThisThread::get_id()); MBED_ASSERT(_mutex._count == 1); @@ -55,7 +63,7 @@ _mutex.unlock(); - bool timeout = !current_thread.sem.try_acquire_for(millisec); + cv_status status = current_thread.sem.try_acquire_for(rel_time) ? cv_status::no_timeout : cv_status::timeout; _mutex.lock(); @@ -63,24 +71,29 @@ _remove_wait_list(&_wait_list, ¤t_thread); } - return timeout; + return status; } bool ConditionVariable::wait_until(uint64_t millisec) { - uint64_t now = Kernel::get_ms_count(); + return wait_until(Kernel::Clock::time_point(duration(millisec))) == cv_status::timeout; +} - if (now >= millisec) { +cv_status ConditionVariable::wait_until(Kernel::Clock::time_point abs_time) +{ + Kernel::Clock::time_point now = Kernel::Clock::now(); + + if (now >= abs_time) { // Time has already passed - standard behaviour is to // treat as a "try". - return wait_for(0); - } else if (millisec - now >= osWaitForever) { + return wait_for(Kernel::Clock::duration_u32::zero()); + } else if (abs_time - now > Kernel::wait_for_u32_max) { // Exceeds maximum delay of underlying wait_for - // spuriously wake after 49 days, indicating no timeout. - wait_for(osWaitForever - 1); - return false; + wait_for(Kernel::wait_for_u32_max); + return cv_status::no_timeout; } else { - return wait_for(millisec - now); + return wait_for(abs_time - now); } }