Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
Thread.inl
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include <atomic>
5
8#include "Stroika/Foundation/Execution/WaitableEvent.h"
9
11
12 /*
13 ********************************************************************************
14 ********************************* Thread::Ptr::Rep_ ****************************
15 ********************************************************************************
16 */
17 /**
18 * \note \em Thread-Safety <a href="Thread-Safety.md#Internally-Synchronized-Thread-Safety">Internally-Synchronized-Thread-Safety</a>
19 */
20 class Thread::Ptr::Rep_ {
21 public:
22 Rep_ (const function<void ()>& runnable, const optional<Configuration>& configuration);
23 ~Rep_ ();
24
25 public:
26 nonvirtual IDType GetID () const;
27
28 private:
29 // same as GetStatus but using Peek so avoids some locks, and can be used in ToString() without causing that to lock stuff...
30 // BEWARE calling PeekStatusForToString_ - as its values returned CAN BE STALE
31 nonvirtual Status PeekStatusForToString_ () const noexcept;
32 nonvirtual bool IsDone_ () const noexcept;
33
34 public:
35 nonvirtual NativeHandleType GetNativeHandle ();
36
37#if __cpp_lib_jthread >= 201911
38 public:
39 nonvirtual stop_token GetStopToken () const;
40#endif
41
42 public:
43 nonvirtual Characters::String ToString () const;
44
45 public:
46 nonvirtual void ApplyThreadName2OSThreadObject ();
47
48 public:
49 nonvirtual void ApplyPriority (Priority priority);
50
51 private:
52 nonvirtual void Run_ ();
53
54 private:
55 // Called - typically from ANOTHER thread (but could be this thread). By default this does nothing,
56 // and is just called by Thread::Abort (). It sets (indirectly) the thread-local-storage interrupted
57 // flag for the target thread. And if called from an aborting thread, it may throw
58 nonvirtual void NotifyOfInterruptionFromAnyThread_ ();
59
60 private:
61 static void ThreadMain_ (const shared_ptr<Rep_> thisThreadRep) noexcept;
62
63 private:
64#if qStroika_Foundation_Common_Platform_POSIX
65 static void InterruptionSignalHandler_ (SignalID signal) noexcept;
66#elif qStroika_Foundation_Common_Platform_Windows
67 static void CALLBACK CalledInRepThreadAbortProc_ (ULONG_PTR lpParameter);
68#endif
69
70 private:
71 function<void ()> fRunnable_;
72 atomic<bool> fAbortRequested_{false}; // regular interrupt, abort interrupt, or none
73 // Before Stroika v3.0d5 - we had a mutex to protect fThread_ object, but it caused more trouble than it solved, so removed
74#if __cpp_lib_jthread >= 201911
75 stop_source fStopSource_;
76 stop_token fStopToken_; // initialized in Ptr::Start() before ThreadMain_ called
77 jthread fThread_;
78#else
79 thread fThread_;
80#endif
81 atomic<bool> fStartEverInitiated_{false};
82 atomic<bool> fThreadValid_{false}; // protect a few accesses to fThread so we dont check if until assignment definitely completed
83 WaitableEvent fRefCountBumpedInsideThreadMainEvent_; // see "Stroika thread-start choreography" for description of these events
84 WaitableEvent fStartReadyToTransitionToRunningEvent_;
85 WaitableEvent fThreadDoneAndCanJoin_;
86 wstring fThreadName_;
87 Synchronized<exception_ptr> fSavedException_; // really no logical need for Synchronized<>, except when used from ToString() for debugging
88 Synchronized<optional<Priority>> fInitialPriority_; // where we store priority before start
89#if qStroika_Foundation_Common_Platform_Windows
90 bool fThrowInterruptExceptionInsideUserAPC_{false};
91#endif
92
93 private:
94 friend class Ptr;
95 friend void CheckForInterruption ();
96 };
97
98 /*
99 ********************************************************************************
100 *********************************** Thread::Rep_ *******************************
101 ********************************************************************************
102 */
103#if __cpp_lib_jthread >= 201911
104 inline stop_token Thread::Ptr::Rep_::GetStopToken () const
105 {
106 return this->fStopToken_;
107 }
108#endif
109 inline Thread::IDType Thread::Ptr::Rep_::GetID () const
110 {
111 return fThread_.get_id ();
112 }
113 inline Thread::NativeHandleType Thread::Ptr::Rep_::GetNativeHandle ()
114 {
115 return fThread_.native_handle ();
116 }
117 // [[nosanitize thread]] because PeekIsSet () - intentionally - reads without a lock
118 Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_THREAD inline Thread::Status Thread::Ptr::Rep_::PeekStatusForToString_ () const noexcept
119 {
120 if (fThreadDoneAndCanJoin_.PeekIsSet ()) {
121 return Status::eCompleted;
122 }
123 if (fAbortRequested_) {
124 return Status::eAborting;
125 }
126 if (fStartReadyToTransitionToRunningEvent_.PeekIsSet ()) {
127 return Status::eRunning;
128 }
129 return Status::eNotYetRunning;
130 }
131 inline bool Thread::Ptr::Rep_::IsDone_ () const noexcept
132 {
133 return fThreadDoneAndCanJoin_.GetIsSet ();
134 }
135
136 /*
137 ********************************************************************************
138 ******************************** Thread::Ptr ***********************************
139 ********************************************************************************
140 */
141 inline Thread::Ptr::Ptr (nullptr_t)
142 {
143 }
144 inline Thread::Ptr::Ptr (const shared_ptr<Rep_>& rep)
145 : fRep_{rep}
146 {
147 }
148 inline Thread::Ptr::Ptr (const Ptr& src)
149 : fRep_{src.fRep_}
150 {
151 }
152 inline Thread::Ptr::Ptr (Ptr&& src) noexcept
153 : fRep_{move (src.fRep_)}
154 {
155 }
156 inline Thread::Ptr& Thread::Ptr::operator= (const Ptr& rhs)
157 {
158 Debug::AssertExternallySynchronizedMutex::ReadContext readLock1{rhs.fThisAssertExternallySynchronized_};
159 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
160 fRep_ = rhs.fRep_;
161 return *this;
162 }
163 inline Thread::Ptr& Thread::Ptr::operator= (Ptr&& rhs) noexcept
164 {
165 Debug::AssertExternallySynchronizedMutex::WriteContext declareWriteContext1{rhs.fThisAssertExternallySynchronized_};
166 Debug::AssertExternallySynchronizedMutex::WriteContext declareWriteContext2{fThisAssertExternallySynchronized_};
167 fRep_ = move (rhs.fRep_);
168 return *this;
169 }
170 inline Thread::IDType Thread::Ptr::GetID () const
171 {
172 Debug::AssertExternallySynchronizedMutex::ReadContext declareReadContext{fThisAssertExternallySynchronized_};
173 if (fRep_ == nullptr) [[unlikely]] {
174 return IDType{};
175 }
176 return fRep_->GetID ();
177 }
178 inline Thread::NativeHandleType Thread::Ptr::GetNativeHandle () const noexcept
179 {
180 Debug::AssertExternallySynchronizedMutex::ReadContext declareReadContext{fThisAssertExternallySynchronized_};
181 if (fRep_ == nullptr) [[unlikely]] {
182 return NativeHandleType{};
183 }
184 return fRep_->GetNativeHandle ();
185 }
186#if __cpp_lib_jthread >= 201911
187 inline stop_token Thread::Ptr::GetStopToken () const
188 {
189 Debug::AssertExternallySynchronizedMutex::ReadContext declareReadContext{fThisAssertExternallySynchronized_};
190 RequireNotNull (fRep_);
191 return fRep_->GetStopToken ();
192 }
193#endif
194 inline void Thread::Ptr::reset () noexcept
195 {
196 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized_};
197 fRep_.reset ();
198 }
199 inline function<void ()> Thread::Ptr::GetFunction () const
200 {
201 Debug::AssertExternallySynchronizedMutex::ReadContext declareReadContext{fThisAssertExternallySynchronized_};
202 if (fRep_ == nullptr) [[unlikely]] {
203 return nullptr;
204 }
205 return fRep_->fRunnable_;
206 }
207 inline bool Thread::Ptr::operator== (const Ptr& rhs) const
208 {
209 Debug::AssertExternallySynchronizedMutex::ReadContext readLock1{fThisAssertExternallySynchronized_};
210 Debug::AssertExternallySynchronizedMutex::ReadContext readLock2{rhs.fThisAssertExternallySynchronized_};
211 return fRep_ == rhs.fRep_;
212 }
213 inline bool Thread::Ptr::operator== (nullptr_t) const
214 {
215 Debug::AssertExternallySynchronizedMutex::ReadContext readLock1{fThisAssertExternallySynchronized_};
216 return fRep_ == nullptr;
217 }
218 inline strong_ordering Thread::Ptr::operator<=> (const Ptr& rhs) const
219 {
220 Debug::AssertExternallySynchronizedMutex::ReadContext readLock1{fThisAssertExternallySynchronized_};
221 Debug::AssertExternallySynchronizedMutex::ReadContext readLock2{rhs.fThisAssertExternallySynchronized_};
222 return Foundation::Common::StdCompat::compare_three_way{}(fRep_, rhs.fRep_);
223 }
224 inline strong_ordering Thread::Ptr::operator<=> (nullptr_t) const
225 {
226 Debug::AssertExternallySynchronizedMutex::ReadContext readLock1{fThisAssertExternallySynchronized_};
227#if qCompilerAndStdLib_stdlib_compare_three_way_present_but_Buggy
228 return Foundation::Common::StdCompat::compare_three_way{}(fRep_, nullptr);
229#else
230 return fRep_ <=> nullptr;
231#endif
232 }
233 inline Thread::Ptr::operator bool () const
234 {
235 Debug::AssertExternallySynchronizedMutex::ReadContext declareReadContext{fThisAssertExternallySynchronized_};
236 return fRep_ != nullptr;
237 }
238#if qStroika_Foundation_Common_Platform_Windows
239 inline bool Thread::Ptr::ThrowInterruptExceptionInsideUserAPC () const noexcept
240 {
241 Debug::AssertExternallySynchronizedMutex::ReadContext declareReadContext{fThisAssertExternallySynchronized_};
242 return fRep_ == nullptr ? false : fRep_->fThrowInterruptExceptionInsideUserAPC_;
243 }
244 inline bool Thread::Ptr::ThrowInterruptExceptionInsideUserAPC (optional<bool> throwInterruptExceptionInsideUserAPC)
245 {
246 Debug::AssertExternallySynchronizedMutex::WriteContext critSec1{fThisAssertExternallySynchronized_};
247 bool result = fRep_ == nullptr ? false : fRep_->fThrowInterruptExceptionInsideUserAPC_;
248 if (throwInterruptExceptionInsideUserAPC) {
249 RequireNotNull (fRep_);
250 fRep_->fThrowInterruptExceptionInsideUserAPC_ = throwInterruptExceptionInsideUserAPC.value ();
251 }
252 return result;
253 }
254#endif
255 inline Thread::Status Thread::Ptr::GetStatus () const noexcept
256 {
257 RequireNotNull (fRep_);
258 Debug::AssertExternallySynchronizedMutex::ReadContext declareReadContext{fThisAssertExternallySynchronized_};
259 if (fRep_->fThreadDoneAndCanJoin_.GetIsSet ()) {
260 return Status::eCompleted;
261 }
262 if (fRep_->fAbortRequested_) {
263 return Status::eAborting;
264 }
265 if (fRep_->fStartReadyToTransitionToRunningEvent_.GetIsSet ()) {
266 return Status::eRunning;
267 }
268 return Status::eNotYetRunning;
269 }
270 inline bool Thread::Ptr::IsDone () const
271 {
272 Debug::AssertExternallySynchronizedMutex::ReadContext declareReadContext{fThisAssertExternallySynchronized_};
273 RequireNotNull (fRep_);
274 return fRep_->fThreadDoneAndCanJoin_.GetIsSet ();
275 }
276 inline void Thread::Ptr::Join (Time::DurationSeconds timeout) const
277 {
278 JoinUntil (timeout + Time::GetTickCount ());
279 }
280 inline void Thread::Ptr::JoinUntil (Time::TimePointSeconds timeoutAt) const
281 {
282 Debug::AssertExternallySynchronizedMutex::ReadContext declareReadContext{fThisAssertExternallySynchronized_};
283 WaitForDoneUntil (timeoutAt);
284 ThrowIfDoneWithException ();
285 }
286 inline void Thread::Ptr::WaitForDone (Time::DurationSeconds timeout) const
287 {
288 Debug::AssertExternallySynchronizedMutex::ReadContext declareReadContext{fThisAssertExternallySynchronized_};
289 WaitForDoneUntil (timeout + Time::GetTickCount ());
290 }
291 inline void Thread::Ptr::AbortAndWaitForDone (Time::DurationSeconds timeout) const
292 {
293 Debug::AssertExternallySynchronizedMutex::ReadContext declareReadContext{fThisAssertExternallySynchronized_};
294 AbortAndWaitForDoneUntil (timeout + Time::GetTickCount ());
295 }
296
297 /*
298 ********************************************************************************
299 ******************************** Thread::CleanupPtr ****************************
300 ********************************************************************************
301 */
302 inline Thread::CleanupPtr::CleanupPtr (AbortFlag abortFlag, Ptr threadPtr)
303 : Ptr{threadPtr}
304 , fAbort_{abortFlag == AbortFlag::eAbortBeforeWaiting}
305 {
306 }
308 {
309 // preserve constructed with fAbort flag
310 Ptr::operator= (rhs);
311 return *this;
312 }
313
314 /*
315 ********************************************************************************
316 ************************************* Thread ***********************************
317 ********************************************************************************
318 */
319 inline Thread::Ptr Thread::New (const function<void ()>& fun2CallOnce, const optional<Configuration>& configuration)
320 {
321 return New (fun2CallOnce, nullopt, configuration);
322 }
323 inline Thread::Ptr Thread::New (const function<void ()>& fun2CallOnce, const Characters::String& name, const optional<Configuration>& configuration)
324 {
325 return New (fun2CallOnce, optional<Characters::String>{name}, configuration);
326 }
327 inline auto Thread::New (const function<void ()>& fun2CallOnce, AutoStartFlag, const optional<Characters::String>& name,
328 const optional<Configuration>& configuration) -> Ptr
329 {
330 Ptr ptr = New (fun2CallOnce, name, configuration);
331 ptr.Start ();
332 return ptr;
333 }
334 inline auto Thread::New (const function<void ()>& fun2CallOnce, AutoStartFlag, const Characters::String& name,
335 const optional<Configuration>& configuration) -> Ptr
336 {
337 return New (fun2CallOnce, AutoStartFlag::eAutoStart, optional<Characters::String>{name}, configuration);
338 }
339 inline auto Thread::New (const function<void ()>& fun2CallOnce, AutoStartFlag, const optional<Configuration>& configuration) -> Ptr
340 {
341 return New (fun2CallOnce, AutoStartFlag::eAutoStart, nullopt, configuration);
342 }
344 {
345 AbortAndWaitForDoneUntil (threads, timeout + Time::GetTickCount ());
346 }
347 inline void Thread::Start (const Traversal::Iterable<Ptr>& threads)
348 {
349 threads.Apply ([] (const Ptr& p) { p.Start (); });
350 }
352 {
353 WaitForDoneUntil (threads, timeout + Time::GetTickCount ());
354 }
355
356 /*
357 ********************************************************************************
358 ************************ Thread::GetCurrentThreadID ****************************
359 ********************************************************************************
360 */
361 inline Thread::IDType Thread::GetCurrentThreadID () noexcept
362 {
363 return this_thread::get_id ();
364 }
365
366 /*
367 ********************************************************************************
368 ***************************** Thread::GetCurrent *******************************
369 ********************************************************************************
370 */
372 {
373#if qCompilerAndStdLib_thread_local_static_inline_twice_Buggy
374 return Ptr{Ptr::sCurrentThreadRep_BWA_ ().lock ()};
375#else
376 return Ptr{Ptr::sCurrentThreadRep_.lock ()};
377#endif
378 }
379
380#if !qCompilerAndStdLib_ThreadLocalInlineDupSymbol_Buggy
381#if __cpp_lib_jthread >= 201911
382 /*
383 ********************************************************************************
384 ************************ Thread::GetCurrentThreadStopToken *********************
385 ********************************************************************************
386 */
387 inline optional<stop_token> Thread::GetCurrentThreadStopToken ()
388 {
389 if (Ptr curThread = GetCurrent ()) {
390 return curThread.GetStopToken ();
391 }
392 else {
393 return nullopt;
394 }
395 }
396#endif
397#endif
398
399#if !qCompilerAndStdLib_ThreadLocalInlineDupSymbol_Buggy
400 /*
401 ********************************************************************************
402 ******************* Thread::IsCurrentThreadInterruptible ***********************
403 ********************************************************************************
404 */
406 {
407#if qCompilerAndStdLib_thread_local_static_inline_twice_Buggy
408 return Ptr::sCurrentThreadRep_BWA_ ().lock () != nullptr;
409#else
410 return Ptr::sCurrentThreadRep_.lock () != nullptr;
411#endif
412 }
413#endif
414
415}
416
418 template <>
419 constexpr EnumNames<Execution::Thread::Status> DefaultNames<Execution::Thread::Status>::k{{{
420 {Execution::Thread::Status::eNotYetRunning, L"Not-Yet-Running"},
421 {Execution::Thread::Status::eRunning, L"Running"},
422 {Execution::Thread::Status::eAborting, L"Aborting"},
423 {Execution::Thread::Status::eCompleted, L"Completed"},
424 }}};
425 template <>
426 constexpr EnumNames<Execution::Thread::Priority> DefaultNames<Execution::Thread::Priority>::k{{{
427 {Execution::Thread::Priority::eLowest, L"Lowest"},
428 {Execution::Thread::Priority::eBelowNormal, L"Below-Normal"},
429 {Execution::Thread::Priority::eNormal, L"Normal"},
430 {Execution::Thread::Priority::eAboveNormal, L"Above-Normal"},
431 {Execution::Thread::Priority::eHighest, L"Highest"},
432 }}};
433}
#define RequireNotNull(p)
Definition Assertions.h:347
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
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
shared_lock< const AssertExternallySynchronizedMutex > ReadContext
Instantiate AssertExternallySynchronizedMutex::ReadContext to designate an area of code where protect...
unique_lock< AssertExternallySynchronizedMutex > WriteContext
Instantiate AssertExternallySynchronizedMutex::WriteContext to designate an area of code where protec...
nonvirtual CleanupPtr & operator=(const Ptr &)
Definition Thread.inl:307
Thread::Ptr is a (unsynchronized) smart pointer referencing an internally synchronized std::thread ob...
Definition Thread.h:334
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
nonvirtual void Apply(const function< void(ArgByValueType< T > item)> &doToElement, Execution::SequencePolicy seq=Execution::SequencePolicy::eDEFAULT) const
Run the argument function (or lambda) on each element of the container.
STRING_TYPE ToString(FLOAT_TYPE f, const ToStringOptions &options={})
Ptr New(const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
Definition Thread.cpp:955
void AbortAndWaitForDoneUntil(const Traversal::Iterable< Ptr > &threads, Time::TimePointSeconds timeoutAt)
Definition Thread.cpp:995
thread::native_handle_type NativeHandleType
Definition Thread.h:222
void Start(const Traversal::Iterable< Ptr > &threads)
Definition Thread.inl:347
void WaitForDone(const Traversal::Iterable< Ptr > &threads, Time::DurationSeconds timeout=Time::kInfinity)
Definition Thread.inl:351
void WaitForDoneUntil(const Traversal::Iterable< Ptr > &threads, Time::TimePointSeconds timeoutAt)
Definition Thread.cpp:1008
void AbortAndWaitForDone(const Traversal::Iterable< Ptr > &threads, Time::DurationSeconds timeout=Time::kInfinity)
shorthand for AbortAndWaitForDoneUntil (Time::GetTickCount () + timeout)
Definition Thread.inl:343