From bb85ad849e525c084aa55e59bc7b1026fee893ac Mon Sep 17 00:00:00 2001 From: Ville Juven Date: Wed, 15 Jan 2025 13:35:23 +0200 Subject: [PATCH] pthread_cond_wait: Use atomic_t to protect the waiter count The load/compare and RMW to wait_count need protection. Using atomic operations should resolve both issues. NOTE: The assumption that the user will call pthread_cond_signal / pthread_cond_broadcast with the mutex given to pthread_cond_wait held is simply not true. It MAY hold it, but it is not forced. Thus, using the user space lock for protecting the wait counter as well is not valid! The pthread_cond_signal() or pthread_cond_broadcast() functions may be called by a thread whether or not it currently owns the mutex that threads calling pthread_cond_wait() or pthread_cond_timedwait() have associated with the condition variable during their waits; however, if predictable scheduling behaviour is required, then that mutex is locked by the thread calling pthread_cond_signal() or pthread_cond_broadcast(). [1] https://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread_cond_signal.html --- include/pthread.h | 2 +- sched/pthread/pthread.h | 2 ++ sched/pthread/pthread_condbroadcast.c | 29 ++++++++++++++------------- sched/pthread/pthread_condclockwait.c | 3 ++- sched/pthread/pthread_condsignal.c | 19 ++++++++++-------- sched/pthread/pthread_condwait.c | 3 ++- 6 files changed, 33 insertions(+), 25 deletions(-) diff --git a/include/pthread.h b/include/pthread.h index 191bbf4e8bf..38fce8d09c5 100644 --- a/include/pthread.h +++ b/include/pthread.h @@ -269,7 +269,7 @@ struct pthread_cond_s { sem_t sem; clockid_t clockid; - uint16_t wait_count; + int wait_count; }; #ifndef __PTHREAD_COND_T_DEFINED diff --git a/sched/pthread/pthread.h b/sched/pthread/pthread.h index d6c3bd76adf..baf48407c3d 100644 --- a/sched/pthread/pthread.h +++ b/sched/pthread/pthread.h @@ -78,6 +78,8 @@ # define mutex_setprioceiling(m,p,o) nxmutex_setprioceiling(m,p,o) #endif +#define COND_WAIT_COUNT(cond) ((FAR atomic_t *)&(cond)->wait_count) + /**************************************************************************** * Public Data ****************************************************************************/ diff --git a/sched/pthread/pthread_condbroadcast.c b/sched/pthread/pthread_condbroadcast.c index 58166654871..f67556009fd 100644 --- a/sched/pthread/pthread_condbroadcast.c +++ b/sched/pthread/pthread_condbroadcast.c @@ -31,6 +31,8 @@ #include #include +#include + #include "pthread/pthread.h" /**************************************************************************** @@ -42,10 +44,7 @@ * * Description: * A thread broadcast on a condition variable. - * pthread_cond_broadcast shall unblock all threads currently blocked on a - * specified condition variable cond. We need own the mutex that threads - * calling pthread_cond_wait or pthread_cond_timedwait have associated - * with the condition variable during their wait. + * * Input Parameters: * None * @@ -68,22 +67,24 @@ int pthread_cond_broadcast(FAR pthread_cond_t *cond) } else { + int wcnt = atomic_read(COND_WAIT_COUNT(cond)); + /* Loop until all of the waiting threads have been restarted. */ - while (cond->wait_count > 0) + while (wcnt > 0) { - /* If the value is less than zero (meaning that one or more - * thread is waiting), then post the condition semaphore. - * Only the highest priority waiting thread will get to execute - */ + if (atomic_cmpxchg(COND_WAIT_COUNT(cond), &wcnt, wcnt - 1)) + { + /* Post the condition semaphore to wake up a waiting thread. + * Only the highest priority waiting thread will get to execute + */ - ret = -nxsem_post(&cond->sem); + ret = -nxsem_post(&cond->sem); - /* Increment the semaphore count (as was done by the - * above post). - */ + /* Decrement the waiter count */ - cond->wait_count--; + wcnt--; + } } } diff --git a/sched/pthread/pthread_condclockwait.c b/sched/pthread/pthread_condclockwait.c index 77a62101f9a..120d72d3f5c 100644 --- a/sched/pthread/pthread_condclockwait.c +++ b/sched/pthread/pthread_condclockwait.c @@ -36,6 +36,7 @@ #include #include +#include #include #include #include @@ -113,7 +114,7 @@ int pthread_cond_clockwait(FAR pthread_cond_t *cond, sinfo("Give up mutex...\n"); - cond->wait_count++; + atomic_fetch_add(COND_WAIT_COUNT(cond), 1); /* Give up the mutex */ diff --git a/sched/pthread/pthread_condsignal.c b/sched/pthread/pthread_condsignal.c index 4e9055f773d..06a362836bc 100644 --- a/sched/pthread/pthread_condsignal.c +++ b/sched/pthread/pthread_condsignal.c @@ -30,6 +30,8 @@ #include #include +#include + #include "pthread/pthread.h" /**************************************************************************** @@ -41,10 +43,6 @@ * * Description: * A thread can signal on a condition variable. - * pthread_cond_signal shall unblock a thread currently blocked on a - * specified condition variable cond. We need own the mutex that threads - * calling pthread_cond_wait or pthread_cond_timedwait have associated - * with the condition variable during their wait. * * Input Parameters: * None @@ -68,11 +66,16 @@ int pthread_cond_signal(FAR pthread_cond_t *cond) } else { - if (cond->wait_count > 0) + int wcnt = atomic_read(COND_WAIT_COUNT(cond)); + + while (wcnt > 0) { - sinfo("Signalling...\n"); - cond->wait_count--; - ret = -nxsem_post(&cond->sem); + if (atomic_cmpxchg(COND_WAIT_COUNT(cond), &wcnt, wcnt - 1)) + { + sinfo("Signalling...\n"); + ret = -nxsem_post(&cond->sem); + break; + } } } diff --git a/sched/pthread/pthread_condwait.c b/sched/pthread/pthread_condwait.c index ca8f05511e4..731599db72e 100644 --- a/sched/pthread/pthread_condwait.c +++ b/sched/pthread/pthread_condwait.c @@ -32,6 +32,7 @@ #include #include +#include #include #include "pthread/pthread.h" @@ -88,7 +89,7 @@ int pthread_cond_wait(FAR pthread_cond_t *cond, FAR pthread_mutex_t *mutex) sinfo("Give up mutex / take cond\n"); - cond->wait_count++; + atomic_fetch_add(COND_WAIT_COUNT(cond), 1); ret = pthread_mutex_breaklock(mutex, &nlocks); status = -nxsem_wait_uninterruptible(&cond->sem);