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