Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
ConditionVariable.inl
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. 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 bool ready = fConditionVariable.wait_until (lock, *ost, Time::Pin2SafeSeconds (timeoutAt), forward<PREDICATE> (readyToWake));
107 while (ost->stop_requested () and not ready) {
108 // tricky case.
109 //
110 // 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
111 // was suppressed by (SuppressInterruptionInContext).
112 //
113 // This function is NOT permitted to return spurious interrupts. Just readyToWake return, or timeout.
114 //
115 // If you find yourself looping here - consider if you really wanted to SuppressInterruptionInContext around this!
116 //
118 if (Time::GetTickCount () > timeoutAt) {
119 return ready; // don't throw here - this API doesn't throw timeout...
120 }
121 // 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)
122 ready = fConditionVariable.wait_until (
123 lock, Time::Pin2SafeSeconds (min (timeoutAt, Time::GetTickCount () + sConditionVariableWaitChunkTime)),
124 forward<PREDICATE> (readyToWake));
125 }
126 return ready;
127 }
128#endif
129 }
130
131 // if kSupportsStopToken not in use (or not in a Stroika thread so this thread not stoppable)
132 while (not readyToWake ()) {
133 Assert (lock.owns_lock ()); // lock owned during readyToWake call and before wait_until call
134 // NB: further checks for interruption happen inside wait_until() called here...
135 //
136 // Another SUBTLE point. we could get here with kSupportsStopToken==false, which might be because of
137 // the kind of condition_variable used, etc (many reasons). Point is - we need to be interrupted
138 // in 3 cases:
139 // o timeout
140 // o interrupted
141 // o readyToWake() (variable it looks at) changes, which happens spontaneously (other thread wakes us toggling lock).
142 //
143 // We DONT need to tweak timeoutAt with sConditionVariableWaitChunkTime (as is done in called wait_until) because
144 // if its possible to handle the interruption case, that's done in called wait_until (possibly using sConditionVariableWaitChunkTime).
145 // 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
146 // again.
147 //
148 if (wait_until (lock, timeoutAt) == cv_status::timeout) {
149 /*
150 * Somewhat ambiguous if this should check readyToWake or just return false. Probably best to check, since the condition is met, and that's
151 * probably more important than the timeout.
152 *
153 * Also - docs in https://en.cppreference.com/w/cpp/thread/condition_variable/wait_until - make it clear this is the right thing todo.
154 */
155 auto result = readyToWake ();
156 Ensure (lock.owns_lock ());
157 return result;
158 }
159 // Maybe a real wakeup, or a spurious one, so just check the readyToWake() predicate again, and keep looping!
160 }
161 Ensure (lock.owns_lock ());
162 return true;
163 }
164 template <typename MUTEX, typename CONDITION_VARIABLE>
166 {
167 Require (lock.owns_lock ());
168 Assert (isinf (timeout.count ()) == isinf ((timeout + Time::GetTickCount ()).time_since_epoch ().count ())); // make sure arithmetic works right with inf
169 return wait_until (lock, timeout + Time::GetTickCount ());
170 }
171 template <typename MUTEX, typename CONDITION_VARIABLE>
172 template <invocable PREDICATE>
173 inline bool ConditionVariable<MUTEX, CONDITION_VARIABLE>::wait_for (LockType& lock, Time::DurationSeconds timeout, PREDICATE&& readyToWake)
174 {
175 Require (lock.owns_lock ());
176 Assert (isinf (timeout.count ()) == isinf ((timeout + Time::GetTickCount ()).time_since_epoch ().count ())); // make sure arithmatic works right with inf
177 return wait_until (lock, timeout + Time::GetTickCount (), forward<PREDICATE> (readyToWake));
178 }
179 template <typename MUTEX, typename CONDITION_VARIABLE>
180 template <invocable FUNCTION>
181 inline void ConditionVariable<MUTEX, CONDITION_VARIABLE>::MutateDataNotifyAll (FUNCTION&& mutatorFunction)
182 {
183 // See https://en.cppreference.com/w/cpp/thread/condition_variable for why we modify the data under the lock
184 // but call the notify_all() after releasing the lock - also https://stackoverflow.com/questions/35775501/c-should-condition-variable-be-notified-under-lock
185 {
186 QuickLockType quickLock{fMutex};
187 mutatorFunction ();
188 }
189 fConditionVariable.notify_all ();
190 }
191 template <typename MUTEX, typename CONDITION_VARIABLE>
192 template <invocable FUNCTION>
193 inline void ConditionVariable<MUTEX, CONDITION_VARIABLE>::MutateDataNotifyOne (FUNCTION&& mutatorFunction)
194 {
195 // See https://en.cppreference.com/w/cpp/thread/condition_variable for why we modify the data under the lock
196 // but call the notify_all() after releasing the lock - also https://stackoverflow.com/questions/35775501/c-should-condition-variable-be-notified-under-lock
197 {
198 QuickLockType quickLock{fMutex};
199 mutatorFunction ();
200 }
201 fConditionVariable.notify_one ();
202 }
203
204}
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)