Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
ConditionVariable.inl
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4
6
8
9 // Declared here instead of including Thread.h - so these must NOT be inline: an inline function must be defined in every
10 // translation unit that uses it, and many that use these never see Thread.inl (optimized non-LTO builds then fail to link)
11 namespace Thread {
14#if defined(__cpp_lib_jthread)
15 optional<stop_token> GetCurrentThreadStopToken ();
16#endif
17 }
18
19 /*
20 ********************************************************************************
21 **************** ConditionVariable<MUTEX, CONDITION_VARIABLE> ******************
22 ********************************************************************************
23 */
24 template <typename MUTEX, typename CONDITION_VARIABLE>
26 {
27 lock.unlock ();
28 notify_one ();
29 }
30 template <typename MUTEX, typename CONDITION_VARIABLE>
32 {
33 lock.unlock ();
34 notify_all ();
35 }
36 template <typename MUTEX, typename CONDITION_VARIABLE>
38 {
39 fConditionVariable.notify_one ();
40 }
41 template <typename MUTEX, typename CONDITION_VARIABLE>
43 {
44 fConditionVariable.notify_all ();
45 }
46 template <typename MUTEX, typename CONDITION_VARIABLE>
48 {
49 /*
50 * NOTE: this overload CAN return spurrious wakeups, and just checks timeoutAt to see which cv_status to return.
51 */
52 Require (lock.owns_lock ());
53
55 if (Time::GetTickCount () > timeoutAt) [[unlikely]] {
56 Ensure (lock.owns_lock ());
57 return cv_status::timeout;
58 }
59
60 auto timeoutAtStopPoint = timeoutAt;
61
62 if constexpr (kSupportsStopToken) {
63 // If no predicate function is provided (to say when we are done) - use stop_requested() as the predicate
64#if defined(__cpp_lib_jthread)
65 if (optional<stop_token> ost = Thread::GetCurrentThreadStopToken ()) {
66 if (fConditionVariable.wait_until (lock, *ost, Time::Pin2SafeSeconds (timeoutAt), [&] () { return ost->stop_requested (); }))
67 [[unlikely]] {
69 }
70 return (Time::GetTickCount () < timeoutAt) ? cv_status::no_timeout : cv_status::timeout;
71 }
72#endif
73 }
74
75 // Not all threads are interruptible. For example, the 'main' thread cannot be interrupted or aborted
76 // @todo NOTE - this is a defect compared to Stroika v2.1 interruption - where you could interrupted but not usefully abort the main thread)
77 // But if kSupportsStopToken, and the current thread supports interruption, we don't get here. So just check the other case
78 bool currentThreadIsInterruptible = (not kSupportsStopToken) and Thread::IsCurrentThreadInterruptible ();
79 Assert (not kSupportsStopToken or not currentThreadIsInterruptible); // just cuz of tests above
80
81 if (currentThreadIsInterruptible) {
82 timeoutAtStopPoint = min (timeoutAt, Time::GetTickCount () + sConditionVariableWaitChunkTime);
83 }
84
85 Assert (lock.owns_lock ());
86
87 // If for some reason, we cannot use the stop token (old c++, on main thread or not Stroika thread, or not using condition_variable_any)
88 // fall back on basic condition variable code
89 (void)fConditionVariable.wait_until (lock, Time::Pin2SafeSeconds (timeoutAtStopPoint));
90 Ensure (lock.owns_lock ());
91
92 // cannot use fConditionVariable.wait_until result because we may have fiddled its timeoutAtStopPoint
93 // can be spurious wakeup, or real, no way to know
94 return (Time::GetTickCount () > timeoutAt) ? cv_status::timeout : cv_status::no_timeout;
95 }
96 template <typename MUTEX, typename CONDITION_VARIABLE>
97 template <invocable PREDICATE>
98 bool ConditionVariable<MUTEX, CONDITION_VARIABLE>::wait_until (LockType& lock, Time::TimePointSeconds timeoutAt, PREDICATE&& readyToWake)
99 {
100 Require (lock.owns_lock ());
102
103 // Support interruption using the c++20 stop token API, if possible (supported and we are called from a thread with a Thread object
104 // whose stop_token we can access)
105 if constexpr (kSupportsStopToken) {
106#if defined(__cpp_lib_jthread)
107 if (optional<stop_token> ost = Thread::GetCurrentThreadStopToken ()) {
108 // NB: 'readyToWake' is passed as a plain lvalue here and in the re-wait below, never forwarded:
109 // wait_until () takes its Predicate BY VALUE, and the re-wait sits in a loop, so forwarding would
110 // move out of the caller's predicate and then keep re-reading the moved-from object.
111 bool ready = fConditionVariable.wait_until (lock, *ost, Time::Pin2SafeSeconds (timeoutAt), readyToWake);
112 while (ost->stop_requested () and not ready) {
113 // tricky case.
114 //
115 // We are blocking, waiting for a signaled condition. This thread has been asked to stop. But the only reason why we wouldn't throw in the CheckForInterruption is that it
116 // was suppressed by (SuppressInterruptionInContext).
117 //
118 // This function is NOT permitted to return spurious interrupts. Just readyToWake return, or timeout.
119 //
120 // If you find yourself looping here - consider if you really wanted to SuppressInterruptionInContext around this!
121 //
123 if (Time::GetTickCount () > timeoutAt) {
124 return ready; // don't throw here - this API doesn't throw timeout...
125 }
126 // must recheck / re-wait ONLY on the condition var itself - no stop token (cuz then this instantly returns and doesn't unlock argument lock so the signaler can progress)
127 ready = fConditionVariable.wait_until (
128 lock, Time::Pin2SafeSeconds (min (timeoutAt, Time::GetTickCount () + sConditionVariableWaitChunkTime)), readyToWake);
129 }
130 return ready;
131 }
132#endif
133 }
134
135 // if kSupportsStopToken not in use (or not in a Stroika thread so this thread not stoppable)
136 while (not readyToWake ()) {
137 Assert (lock.owns_lock ()); // lock owned during readyToWake call and before wait_until call
138 // NB: further checks for interruption happen inside wait_until() called here...
139 //
140 // Another SUBTLE point. we could get here with kSupportsStopToken==false, which might be because of
141 // the kind of condition_variable used, etc (many reasons). Point is - we need to be interrupted
142 // in 3 cases:
143 // o timeout
144 // o interrupted
145 // o readyToWake() (variable it looks at) changes, which happens spontaneously (other thread wakes us toggling lock).
146 //
147 // We DONT need to tweak timeoutAt with sConditionVariableWaitChunkTime (as is done in called wait_until) because
148 // if its possible to handle the interruption case, that's done in called wait_until (possibly using sConditionVariableWaitChunkTime).
149 // if we are woken because of a toggle of lock, we'll get (apparently from point of view of called wait_until) spurious wakeup and can check
150 // again.
151 //
152 if (wait_until (lock, timeoutAt) == cv_status::timeout) {
153 /*
154 * Somewhat ambiguous if this should check readyToWake or just return false. Probably best to check, since the condition is met, and that's
155 * probably more important than the timeout.
156 *
157 * Also - docs in https://en.cppreference.com/w/cpp/thread/condition_variable/wait_until - make it clear this is the right thing todo.
158 */
159 auto result = readyToWake ();
160 Ensure (lock.owns_lock ());
161 return result;
162 }
163 // Maybe a real wakeup, or a spurious one, so just check the readyToWake() predicate again, and keep looping!
164 }
165 Ensure (lock.owns_lock ());
166 return true;
167 }
168 template <typename MUTEX, typename CONDITION_VARIABLE>
170 {
171 Require (lock.owns_lock ());
172 Assert (isinf (timeout.count ()) == isinf ((timeout + Time::GetTickCount ()).time_since_epoch ().count ())); // make sure arithmetic works right with inf
173 return wait_until (lock, timeout + Time::GetTickCount ());
174 }
175 template <typename MUTEX, typename CONDITION_VARIABLE>
176 template <invocable PREDICATE>
177 inline bool ConditionVariable<MUTEX, CONDITION_VARIABLE>::wait_for (LockType& lock, Time::DurationSeconds timeout, PREDICATE&& readyToWake)
178 {
179 Require (lock.owns_lock ());
180 Assert (isinf (timeout.count ()) == isinf ((timeout + Time::GetTickCount ()).time_since_epoch ().count ())); // make sure arithmatic works right with inf
181 return wait_until (lock, timeout + Time::GetTickCount (), forward<PREDICATE> (readyToWake));
182 }
183 template <typename MUTEX, typename CONDITION_VARIABLE>
184 template <invocable FUNCTION>
185 inline void ConditionVariable<MUTEX, CONDITION_VARIABLE>::MutateDataNotifyAll (FUNCTION&& mutatorFunction)
186 {
187 // See https://en.cppreference.com/w/cpp/thread/condition_variable for why we modify the data under the lock
188 // but call the notify_all() after releasing the lock - also https://stackoverflow.com/questions/35775501/c-should-condition-variable-be-notified-under-lock
189 {
190 QuickLockType quickLock{fMutex};
191 mutatorFunction ();
192 }
193 fConditionVariable.notify_all ();
194 }
195 template <typename MUTEX, typename CONDITION_VARIABLE>
196 template <invocable FUNCTION>
197 inline void ConditionVariable<MUTEX, CONDITION_VARIABLE>::MutateDataNotifyOne (FUNCTION&& mutatorFunction)
198 {
199 // See https://en.cppreference.com/w/cpp/thread/condition_variable for why we modify the data under the lock
200 // but call the notify_all() after releasing the lock - also https://stackoverflow.com/questions/35775501/c-should-condition-variable-be-notified-under-lock
201 {
202 QuickLockType quickLock{fMutex};
203 mutatorFunction ();
204 }
205 fConditionVariable.notify_one ();
206 }
207
208}
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
Definition Realtime.h:82
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Definition Realtime.h:57
nonvirtual void notify_all() noexcept
forward notify_all () call to underlying std::condition_variable'
nonvirtual void release_and_notify_one(LockType &lock) noexcept
nonvirtual cv_status wait_until(LockType &lock, Time::TimePointSeconds timeoutAt)
nonvirtual void MutateDataNotifyAll(FUNCTION &&mutatorFunction)
nonvirtual cv_status wait_for(LockType &lock, Time::DurationSeconds timeout)
nonvirtual void notify_one() noexcept
forward notify_one () call to underlying std::condition_variable'
nonvirtual void release_and_notify_all(LockType &lock) noexcept
nonvirtual void MutateDataNotifyOne(FUNCTION &&mutatorFunction)