Add Chrono and predicate support to ConditionVariable

pull/12425/head
Kevin Bracey 2020-02-12 15:31:17 +02:00
parent f4e0ea2c75
commit fdc697ee85
2 changed files with 231 additions and 11 deletions

View File

@ -24,6 +24,7 @@
#define CONDITIONVARIABLE_H
#include <stdint.h>
#include <utility>
#include "rtos/mbed_rtos_types.h"
#include "rtos/Mutex.h"
#include "rtos/Semaphore.h"
@ -36,6 +37,11 @@ namespace rtos {
/** \addtogroup rtos-public-api */
/** @{*/
enum class cv_status {
no_timeout,
timeout
};
struct Waiter;
/**
* \defgroup rtos_ConditionVariable ConditionVariable class
@ -178,7 +184,7 @@ public:
* 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 @@ public:
*/
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 <typename Predicate>
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 @@ public:
* @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 <class Predicate>
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 @@ public:
* @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 <class Predicate>
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

View File

@ -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,10 +46,15 @@ ConditionVariable::ConditionVariable(Mutex &mutex): _mutex(mutex), _wait_list(nu
void ConditionVariable::wait()
{
wait_for(osWaitForever);
wait_for(Kernel::wait_for_u32_forever);
}
bool ConditionVariable::wait_for(uint32_t millisec)
{
return wait_for(duration<uint32_t, milli>(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());
@ -55,7 +63,7 @@ bool ConditionVariable::wait_for(uint32_t millisec)
_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 @@ bool ConditionVariable::wait_for(uint32_t millisec)
_remove_wait_list(&_wait_list, &current_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<uint64_t, milli>(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);
}
}