diff --git a/Doxyfile b/Doxyfile index 0389113ce2aae6fae062e626e2f6a5cb96b53b84..e3cff65eeb429bbabdd634f6daa68d8f44bc240d 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1493,7 +1493,7 @@ INCLUDE_FILE_PATTERNS = # undefined via #undef or recursively expanded use the := operator # instead of the = operator. -PREDEFINED = +PREDEFINED = __cplusplus # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. diff --git a/include/osv/condvar.h b/include/osv/condvar.h index be4a629d1ac749a1381b2c92ddbaa2c08ee2ccb6..03a3dfb33eb572a65447a68944477f0a5c043998 100644 --- a/include/osv/condvar.h +++ b/include/osv/condvar.h @@ -5,7 +5,7 @@ * BSD license as described in the LICENSE file in the top-level directory. */ -// condvar is OSv's implemenation of the classic "condition variable" +// condvar is OSv's implementation of the classic "condition variable" // synchronization primitive. It is similar to condition variables in POSIX // Threads, but makes one additional guarantees that POSIX Threads do not: // Our condvar_wait() is guaranteed not to have "spurious wakeups", i.e., @@ -58,6 +58,27 @@ struct wait_record; +/** + * Condition Variable + * + * condvar implements the classic "condition variable" synchronization + * primitive, which together with a mutex is used to allow threads to wait + * until a certain condition occurs. + * + * condvar is similar to condition variables in POSIX Threads, but makes one + * additional guarantees that POSIX Threads do not: Our wait() is + * guaranteed not to have "spurious wakeups", i.e., condvar_wait() will only + * return if someone called condvar_wake_one()/all(). + * + * This difference is subtle. Usually you still need to check the condition + * after condvar_wait() returns because other threads may race us to change + * the condition. But in some cases we use this guarantee. One example is our + * semaphore implementation (semaphore.cc): In the traditional implementation + * when spurious wait() wakeups are possible, the wakee decrements the + * semaphore's counter() this can cause a "thundering herd" problem). Using + * our condvar without spurious wakeups, it is possible to decrement the + * counter in the waker, and decide exactly who to wake. + */ typedef struct condvar { mutex_t m; struct { @@ -79,9 +100,60 @@ typedef struct condvar { #ifdef __cplusplus // In C++, for convenience also provide methods. condvar() { memset(this, 0, sizeof *this); } + /** + * Wait on the condition variable, or timer to expire + * + * Wait to be woken (with wake_one() or wake_all()), or the given timer + * to expire, whichever occurs first. If the timer is nullptr, or omitted, + * wait indefinitely until woken. + * + * It is assumed that wait() is called with the given mutex locked. + * This mutex is unlocked during the wait, and re-locked before wait() + * returns. + * + * The current implementation assumes (as do Posix Threads) that when + * multiple threads wait on the same condition variable concurrently, + * they all do it with the same mutex. If two threads concurrently use + * two different mutexes to wait on the same condition variable, the + * results are undefined (currently, it causes an assertion failure). + * + * \return 0 if woken by a wake_one() or wake_all(), or ETIMEOUT (!=0) + * if was not woken, but the timer expired. Note if a wakeup and the + * timeout race, by the time wait() returns the timer may have already + * expired even if 0 is returned. What a return of 0 means is not that + * a timeout hasn't yet occurred, but rather that one wake_one()/wake_all() + * was consumed to wake us. + */ inline int wait(mutex_t *user_mutex, sched::timer *tmr = nullptr); + /** + * \overload + */ inline int wait(mutex_t &user_mutex, sched::timer *tmr = nullptr); + /** + * Wake one thread waiting on the condition variable + * + * Wake one of the threads currently waiting on the condition variable, + * or do nothing if there is no thread waiting. + * + * wake_one() may be called with the mutex either locked, or unlocked. + * If the mutex is locked, the target thread will not be actually woken + * until the waking thread unlocks it and the target thread can have it. + * This optimization is known as "wait morphing". + */ inline void wake_one(); + /** + * Wake all threads waiting on the condition variable + * + * Wake all of the threads currently waiting on the condition variable, + * or do nothing if there is no thread waiting. + * + * If more than one thread is waiting, they will not all be woken + * concurrently as all will need the same mutex and most will need to + * go back to sleep (this is known as the "thundering herd problem"). + * Rather, only one thread is woken, and the rest are moved to the + * waiting list of the mutex, to be woken up one by one as the mutex + * becomes available. This optimization is known as "wait morphing". + */ inline void wake_all(); template <class Pred> void wait_until(mutex& mtx, Pred pred);