1212#include < errno.h>
1313#include < pthread.h>
1414#include < sys/time.h>
15+ #include < atomic>
16+ #include < memory>
1517#ifdef WFMO
1618#include < algorithm>
1719#include < deque>
@@ -54,7 +56,7 @@ namespace neosmart {
5456 pthread_cond_t CVariable;
5557 pthread_mutex_t Mutex;
5658 bool AutoReset;
57- bool State;
59+ std::atomic< bool > State;
5860#ifdef WFMO
5961 std::deque<neosmart_wfmo_info_t_> RegisteredWaits;
6062#endif
@@ -100,7 +102,8 @@ namespace neosmart {
100102 result = pthread_mutex_init (&event->Mutex , 0 );
101103 assert (result == 0 );
102104
103- event->State = false ;
105+ // memory_order_relaxed: Newly created event is guaranteed to not have any waiters
106+ event->State .store (false , std::memory_order_relaxed);
104107 event->AutoReset = !manualReset;
105108
106109 if (initialState) {
@@ -113,7 +116,8 @@ namespace neosmart {
113116
114117 static int UnlockedWaitForEvent (neosmart_event_t event, uint64_t milliseconds) {
115118 int result = 0 ;
116- if (!event->State ) {
119+ // memory_order_relaxed: unlocking/ordering is guaranteed prior to calling this function
120+ if (!event->State .load (std::memory_order_relaxed)) {
117121 // Zero-timeout event state check optimization
118122 if (milliseconds == 0 ) {
119123 return WAIT_TIMEOUT;
@@ -139,17 +143,20 @@ namespace neosmart {
139143 } else {
140144 result = pthread_cond_wait (&event->CVariable , &event->Mutex );
141145 }
142- } while (result == 0 && !event->State );
146+ // memory_order_relaxed: ordering is guaranteed by the mutex
147+ } while (result == 0 && !event->State .load (std::memory_order_relaxed));
143148
144149 if (result == 0 && event->AutoReset ) {
145150 // We've only accquired the event if the wait succeeded
146- event->State = false ;
151+ // memory_order_release: Prevent overlapping/interleaved Set/Reset contexts
152+ event->State .store (false , std::memory_order_release);
147153 }
148154 } else if (event->AutoReset ) {
149155 // It's an auto-reset event that's currently available;
150156 // we need to stop anyone else from using it
151157 result = 0 ;
152- event->State = false ;
158+ // memory_order_release: Prevent overlapping/interleaved Set/Reset contexts
159+ event->State .store (false , std::memory_order_release);
153160 }
154161 // Else we're trying to obtain a manual reset event with a signaled state;
155162 // don't do anything
@@ -158,21 +165,19 @@ namespace neosmart {
158165 }
159166
160167 int WaitForEvent (neosmart_event_t event, uint64_t milliseconds) {
161- int tempResult;
162- if (milliseconds == 0 ) {
163- tempResult = pthread_mutex_trylock (&event->Mutex );
164- if (tempResult == EBUSY) {
165- return WAIT_TIMEOUT;
166- }
168+ // Optimization: bypass acquiring the event lock if the state atomic is unavailable.
169+ // memory_order_relaxed: This is just an optimization, it's OK to be biased towards a stale
170+ // value here, and preferable to synchronizing CPU caches to get a more accurate result.
171+ if (milliseconds == 0 && !event->State .load (std::memory_order_relaxed)) {
172+ return WAIT_TIMEOUT;
167173 } else {
168- tempResult = pthread_mutex_lock (&event->Mutex );
174+ int result = pthread_mutex_lock (&event->Mutex );
175+ assert (result == 0 );
169176 }
170177
171- assert (tempResult == 0 );
172-
173178 int result = UnlockedWaitForEvent (event, milliseconds);
174179
175- tempResult = pthread_mutex_unlock (&event->Mutex );
180+ int tempResult = pthread_mutex_unlock (&event->Mutex );
176181 assert (tempResult == 0 );
177182
178183 return result;
@@ -344,7 +349,8 @@ namespace neosmart {
344349 int result = pthread_mutex_lock (&event->Mutex );
345350 assert (result == 0 );
346351
347- event->State = true ;
352+ // memory_order_release: Unblock threads waiting for the event
353+ event->State .store (true , std::memory_order_release);
348354
349355 // Depending on the event type, we either trigger everyone or only one
350356 if (event->AutoReset ) {
@@ -369,8 +375,6 @@ namespace neosmart {
369375 continue ;
370376 }
371377
372- event->State = false ;
373-
374378 if (i->Waiter ->WaitAll ) {
375379 --i->Waiter ->Status .EventsLeft ;
376380 assert (i->Waiter ->Status .EventsLeft >= 0 );
@@ -391,14 +395,18 @@ namespace neosmart {
391395
392396 event->RegisteredWaits .pop_front ();
393397
398+ // memory_order_release: Prevent overlapping of sequential Set/Reset states.
399+ event->State .store (false , std::memory_order_release);
400+
394401 result = pthread_mutex_unlock (&event->Mutex );
395402 assert (result == 0 );
396403
397404 return 0 ;
398405 }
399406#endif // WFMO
400- // event->State can be false if compiled with WFMO support
401- if (event->State ) {
407+ // event->State can be false if compiled with WFMO support
408+ // memory_order_relaxed: ordering is ensured by the mutex
409+ if (event->State .load (std::memory_order_relaxed)) {
402410 result = pthread_mutex_unlock (&event->Mutex );
403411 assert (result == 0 );
404412
@@ -407,7 +415,7 @@ namespace neosmart {
407415
408416 return 0 ;
409417 }
410- } else {
418+ } else { // this is a manual reset event
411419#ifdef WFMO
412420 for (size_t i = 0 ; i < event->RegisteredWaits .size (); ++i) {
413421 neosmart_wfmo_info_t info = &event->RegisteredWaits [i];
@@ -463,7 +471,9 @@ namespace neosmart {
463471 int result = pthread_mutex_lock (&event->Mutex );
464472 assert (result == 0 );
465473
466- event->State = false ;
474+ // memory_order_release: Prevent sequential Set/Reset calls from overlapping, this seems to
475+ // be required per https://old.reddit.com/r/cpp/comments/g84bzv/c/fpua2yq/
476+ event->State .store (false , std::memory_order_release);
467477
468478 result = pthread_mutex_unlock (&event->Mutex );
469479 assert (result == 0 );
@@ -493,7 +503,7 @@ namespace neosmart {
493503#endif
494504} // namespace neosmart
495505
496- #else // _WIN32
506+ #else // _WIN32
497507
498508#include < Windows.h>
499509#include " pevents.h"
0 commit comments