Stroika Library 3.0d20
 
Loading...
Searching...
No Matches
Thread.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include "Stroika/Foundation/Common/StroikaConfig.h"
7
8#include <list>
9#include <sstream>
10#if qStroika_Foundation_Common_Platform_Windows
11#include <process.h>
12#include <windows.h>
13#endif
14
19#include "Stroika/Foundation/Containers/Set.h"
20#include "Stroika/Foundation/Debug/Main.h"
23
24#include "Common.h"
25#include "DLLSupport.h"
26#include "Exceptions.h"
27#include "Synchronized.h"
28#include "TimeOutException.h"
29
30#if qStroika_Foundation_Common_Platform_POSIX
31#include "Platform/POSIX/SignalBlock.h"
32#include "SignalHandlers.h"
33#endif
34#if qStroika_Foundation_Common_Platform_Windows
35#include "Platform/Windows/WaitSupport.h"
36#endif
37
38#include "Thread.h"
39
40using namespace Stroika::Foundation;
41
42using Containers::Set;
44
45// Comment this in to turn on aggressive noisy DbgTrace in this module
46// #define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
47
48// Leave this off by default since I'm not sure its safe, and at best it uses some time. But make it
49// easy to turn on it release builds...
50// -- LGP 2009-05-28
51// According to http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx - its best NOT to call this RaiseException call
52// unless a debugger is present. Use IsDebuggerPresent(). Still not perfect.
53//
54//#define qSupportSetThreadNameDebuggerCall_ 0
55#ifndef qSupportSetThreadNameDebuggerCall_
56#if qStroika_Foundation_Debug_AssertionsChecked && qStroika_Foundation_Common_Platform_Windows
57#define qSupportSetThreadNameDebuggerCall_ 1
58#endif
59#endif
60#ifndef qSupportSetThreadNameDebuggerCall_
61#if qStroika_Foundation_Common_Platform_POSIX
62#define qSupportSetThreadNameDebuggerCall_ 1
63#endif
64#endif
65#ifndef qSupportSetThreadNameDebuggerCall_
66#define qSupportSetThreadNameDebuggerCall_ 0
67#endif
68
69using namespace Characters;
70using namespace Execution;
71
72namespace {
73 thread_local unsigned int t_InterruptionSuppressDepth_{0};
74}
75
76#if qStroika_Foundation_Execution_Thread_SupportThreadStatistics
77namespace {
78 // use mutex and set<> to avoid interdependencies between low level Stroika facilities
79 mutex sThreadSupportStatsMutex_;
80 set<Thread::IDType> sRunningThreads_; // protected by sThreadSupportStatsMutex_
81
82 struct AllThreadsDeadDetector_ {
83 AllThreadsDeadDetector_ ()
84 {
85 Require (sRunningThreads_.empty ());
86 }
87 ~AllThreadsDeadDetector_ ()
88 {
90 if (not sRunningThreads_.empty ()) {
91 DbgTrace ("Threads {} running"_f, Thread::GetStatistics ().fRunningThreads);
92 Require (sRunningThreads_.empty ());
93 }
94 }
95 }
96 };
97 AllThreadsDeadDetector_ sAllThreadsDeadDetector_;
98}
99#endif
100
101#if qStroika_Foundation_Common_Platform_Windows
102namespace {
103#if (_WIN32_WINNT < 0x0502)
104 namespace XXX_ {
105 struct CLIENT_ID {
106 DWORD UniqueProcess;
107 DWORD UniqueThread;
108 };
109 using NTSTATUS = LONG;
110#define STATUS_SUCCESS ((NTSTATUS)0x00000000)
111 using KPRIORITY = LONG;
112 struct THREAD_BASIC_INFORMATION {
113 NTSTATUS ExitStatus;
114 PVOID TebBaseAddress;
115 CLIENT_ID ClientId;
116 KAFFINITY AffinityMask;
117 KPRIORITY Priority;
118 KPRIORITY BasePriority;
119 };
120 enum THREAD_INFORMATION_CLASS {
121 ThreadBasicInformation = 0,
122 };
123 using pfnNtQueryInformationThread = NTSTATUS (__stdcall*) (HANDLE, THREAD_INFORMATION_CLASS, PVOID, ULONG, PULONG);
124 }
125#endif
126 DWORD MyGetThreadId_ (HANDLE thread)
127 {
128#if (_WIN32_WINNT >= 0x0502)
129 return ::GetThreadId (thread);
130#else
131 // See details in http://www.codeguru.com/forum/showthread.php?t=355572 on this... - backcompat - only support
132 // GetThreadId (HANDLE) in Win 2003 Server or later
133 using namespace XXX_;
134 static DLLLoader ntdll (SDKSTR ("ntdll.dll"));
135 static pfnNtQueryInformationThread NtQueryInformationThread =
136 (pfnNtQueryInformationThread)ntdll.GetProcAddress ("NtQueryInformationThread");
137 if (NtQueryInformationThread == nullptr)
138 return 0; // failed to get proc address
139 THREAD_BASIC_INFORMATION tbi{};
140 THREAD_INFORMATION_CLASS tic = ThreadBasicInformation;
141 if (::NtQueryInformationThread (thread, tic, &tbi, sizeof (tbi), nullptr) != STATUS_SUCCESS) {
142 return 0;
143 }
144 return tbi.ClientId.UniqueThread;
145#endif
146 }
147}
148#endif
149
151
152#if qStroika_Foundation_Common_Platform_POSIX
153namespace {
154 Synchronized<bool> sHandlerInstalled_{false};
155}
156#endif
157
158#if qStroika_Foundation_Common_Platform_POSIX
159// Important to use direct signal handler because we send the signal to a specific thread, and must set a thread local
160// variable
161SignalHandler kCallInRepThreadAbortProcSignalHandler_ = SIG_IGN;
162#endif
163
164/*
165 ********************************************************************************
166 ************** Thread::SuppressInterruptionInContext ***************************
167 ********************************************************************************
168 */
169Thread::SuppressInterruptionInContext::SuppressInterruptionInContext ()
170{
171 ++t_InterruptionSuppressDepth_;
172}
173
174Thread::SuppressInterruptionInContext::~SuppressInterruptionInContext ()
175{
176 Assert (t_InterruptionSuppressDepth_ >= 1);
177 t_InterruptionSuppressDepth_--;
178 /*
179 * Would LIKE to do:
180 *
181 * if (t_InterruptionSuppressDepth_ == 0 and t_Interrupting_ != InterruptFlagState_::eNone) {
182 * DbgTrace (L"~SuppressInterruptionInContext () completing with interruption pending, so this thread will interupt at the next cancelation point");
183 * }
184 * But cannot safely/easily, because DbgTrace internally uses SuppressInterruptionInContext!
185 */
186}
187
188/*
189 ********************************************************************************
190 ************************** Thread::AbortException ******************************
191 ********************************************************************************
192 */
193Thread::AbortException::AbortException ()
194 : Exception<>{"Thread Abort"sv}
195{
196}
197
198/*
199 ********************************************************************************
200 ************************** Thread::IndexRegistrar ******************************
201 ********************************************************************************
202 */
203Thread::IndexRegistrar::IndexRegistrar ()
204{
205 Assert (not fInitialized_);
206 fInitialized_ = true;
207}
208
209Thread::IndexRegistrar::~IndexRegistrar ()
210{
211 Assert (fInitialized_);
212 fInitialized_ = false;
213}
214
215unsigned int Thread::IndexRegistrar::GetIndex (const IDType& threadID, bool* wasNew)
216{
217 if (not fInitialized_) {
218 if (wasNew != nullptr) {
219 *wasNew = false;
220 }
221 return 0;
222 }
223 [[maybe_unused]] lock_guard critSec{fMutex_};
224 auto i = fShownThreadIDs_.find (threadID);
225 unsigned int threadIndex2Show = 0;
226 if (i == fShownThreadIDs_.end ()) {
227 threadIndex2Show = static_cast<unsigned int> (fShownThreadIDs_.size ());
228 fShownThreadIDs_.insert ({threadID, threadIndex2Show});
229 }
230 else {
231 threadIndex2Show = i->second;
232 }
233 if (wasNew != nullptr) {
234 *wasNew = i == fShownThreadIDs_.end ();
235 }
236 return threadIndex2Show;
237}
238
239/*
240 ********************************************************************************
241 ***************************** Thread::Ptr::Rep_ ********************************
242 ********************************************************************************
243 */
244Thread::Ptr::Rep_::Rep_ (const function<void ()>& runnable, [[maybe_unused]] const optional<Configuration>& configuration)
245 : fRunnable_{runnable}
246{
247 // @todo - never used anything from configuration (yet) - should!)
248#if qStroika_Foundation_Common_Platform_POSIX
249 static bool sDidInit_{false}; // initialize after main() started, but before any threads
250 if (not sDidInit_) {
251 sDidInit_ = true;
252 kCallInRepThreadAbortProcSignalHandler_ = SignalHandler{Rep_::InterruptionSignalHandler_, SignalHandler::Type::eDirect};
253 }
254#elif qStroika_Foundation_Common_Platform_Windows
255 if (configuration.has_value () and configuration->fThrowInterruptExceptionInsideUserAPC.has_value ()) {
256 fThrowInterruptExceptionInsideUserAPC_ = configuration->fThrowInterruptExceptionInsideUserAPC.value ();
257 }
258#endif
259}
260
261Thread::Ptr::Rep_::~Rep_ ()
262{
263 /*
264 * Use thread::detach() - since this could be called from another thread, or from the
265 * thread which fThread_ refers to. Calling from the later case thread would deadlock
266 * and is a C++ error to call.
267 *
268 * thread::detach will cause all resources for the thread to be deleted once the thread
269 * terminates.
270 *
271 * From http://en.cppreference.com/w/cpp/thread/thread/detach:
272 * Separates the thread of execution from the thread object, allowing execution to continue
273 * independently. Any allocated resources will be freed once the thread exits.
274 *
275 * no need for lock_guard<mutex> critSec { fAccessSTDThreadMutex_ }; because if destroying, only one thread can reference this smart-ptr
276 */
277 if (fThreadValid_ and fThread_.joinable ()) {
278 fThread_.detach ();
279 }
280}
281
282void Thread::Ptr::Rep_::Run_ ()
283{
284 try {
285 fRunnable_ ();
286 }
287 catch (const AbortException&) {
288 // Note: intentionally not saved in fSavedException_.
289 // See ThrowIfDoneWithException
290 throw;
291 }
292 catch (...) {
293#if USE_NOISY_TRACE_IN_THIS_MODULE_
294 DbgTrace ("in ad::Ptr::Rep_::Run_ () - saving caught exception to repropagate later ({})"_f, current_exception ());
295#endif
296 fSavedException_ = current_exception ();
297 throw;
298 }
299}
300
301// [[nosanitize thread]] because PeekIsSet () - intentionally - reads without a lock
302Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_THREAD Characters::String Thread::Ptr::Rep_::ToString () const
303{
304 StringBuilder sb;
305 sb << "{"sv;
306 if (fRefCountBumpedInsideThreadMainEvent_.PeekIsSet ()) {
307 // If fRefCountBumpedInsideThreadMainEvent_ not yet SET, then this info is bogus
308 sb << "id: "sv << GetID ();
310 sb << ", index: " << IndexRegistrar::sThe.GetIndex (GetID ());
311 }
312 }
313 if (not fThreadName_.empty ()) {
314 sb << ", name: "sv << fThreadName_;
315 }
316 sb << ", status: "sv << PeekStatusForToString_ ();
317 //sb << ", runnable: "sv << fRunnable_; // doesn't yet print anything useful
318 sb << ", abortRequested: "sv << fAbortRequested_.load ();
319 sb << ", refCountBumpedEvent: "sv << fRefCountBumpedInsideThreadMainEvent_.PeekIsSet ();
320 sb << ", startReadyToTransitionToRunningEvent_: "sv << fStartReadyToTransitionToRunningEvent_.PeekIsSet ();
321 sb << ", threadDoneAndCanJoin: "sv << fThreadDoneAndCanJoin_.PeekIsSet ();
322 if (fSavedException_.load () != nullptr) [[unlikely]] {
323 sb << ", savedException: "sv << fSavedException_.load ();
324 }
325 if (fInitialPriority_.load () != nullopt) [[unlikely]] {
326 sb << ", initialPriority: "sv << fInitialPriority_.load ();
327 }
328#if qStroika_Foundation_Common_Platform_Windows
329 sb << ", throwInterruptExceptionInsideUserAPC: "sv << fThrowInterruptExceptionInsideUserAPC_;
330#endif
331 sb << "}"sv;
332 return sb;
333}
334
335void Thread::Ptr::Rep_::ApplyThreadName2OSThreadObject ()
336{
337 if (GetNativeHandle () != NativeHandleType{}) {
338#if qSupportSetThreadNameDebuggerCall_
339#if qStroika_Foundation_Common_Platform_Windows
340 if (::IsDebuggerPresent ()) {
341 // This hack from http://www.codeproject.com/KB/threads/Name_threads_in_debugger.aspx
342 struct THREADNAME_INFO {
343 DWORD dwType; // must be 0x1000
344 LPCSTR szName; // pointer to name (in user addr space)
345 DWORD dwThreadID; // thread ID (-1=caller thread)
346 DWORD dwFlags; // reserved for future use, must be zero
347 };
348 string useThreadName = String{fThreadName_}.AsNarrowSDKString (eIgnoreErrors);
349 THREADNAME_INFO info;
350 {
351 info.dwType = 0x1000;
352 info.szName = useThreadName.c_str ();
353 info.dwThreadID = MyGetThreadId_ (GetNativeHandle ());
354 info.dwFlags = 0;
355 }
356 IgnoreExceptionsForCall (::RaiseException (0x406D1388, 0, sizeof (info) / sizeof (DWORD), (ULONG_PTR*)&info));
357 }
358#elif qStroika_Foundation_Common_Platform_POSIX && (__GLIBC__ > 2 or (__GLIBC__ == 2 and __GLIBC_MINOR__ >= 12))
359 // could have called prctl(PR_SET_NAME,"<null> terminated string",0,0,0) - but seems less portable
360 //
361 // according to http://man7.org/linux/man-pages/man3/pthread_setname_np.3.html - the length max is 15 characters
362 constexpr size_t kMaxNameLen_{16 - 1}; // 16 chars including nul byte
363 string narrowThreadName = String{fThreadName_}.AsNarrowSDKString (eIgnoreErrors);
364 if (narrowThreadName.length () > kMaxNameLen_) {
365 narrowThreadName.erase (kMaxNameLen_);
366 }
367 ::pthread_setname_np (GetNativeHandle (), narrowThreadName.c_str ());
368#endif
369#endif
370 }
371}
372
373void Thread::Ptr::Rep_::ApplyPriority (Priority priority)
374{
375#if USE_NOISY_TRACE_IN_THIS_MODULE_
377 "Thread::Ptr::Rep_::ApplyPriority", "threads={}, priority={}"_f, Characters::ToString (*this), Characters::ToString (priority))};
378#endif
379 NativeHandleType nh = GetNativeHandle ();
380 if (nh != NativeHandleType{}) {
381#if qStroika_Foundation_Common_Platform_Windows
382 switch (priority) {
383 case Priority::eLowest:
384 Verify (::SetThreadPriority (nh, THREAD_PRIORITY_LOWEST));
385 break;
386 case Priority::eBelowNormal:
387 Verify (::SetThreadPriority (nh, THREAD_PRIORITY_BELOW_NORMAL));
388 break;
389 case Priority::eNormal:
390 Verify (::SetThreadPriority (nh, THREAD_PRIORITY_NORMAL));
391 break;
392 case Priority::eAboveNormal:
393 Verify (::SetThreadPriority (nh, THREAD_PRIORITY_ABOVE_NORMAL));
394 break;
395 case Priority::eHighest:
396 Verify (::SetThreadPriority (nh, THREAD_PRIORITY_HIGHEST));
397 break;
398 default:
400 }
401#elif qStroika_Foundation_Common_Platform_POSIX
402 /*
403 * pthreads - use http://man7.org/linux/man-pages/man3/pthread_getschedparam.3.html
404 *
405 * Linux notes:
406 * From http://man7.org/linux/man-pages/man7/sched.7.html
407 *
408 * Since Linux 2.6.23, the default scheduler is CFS, the "Completely
409 * Fair Scheduler". The CFS scheduler replaced the earlier "O(1)"
410 * scheduler.
411 *
412 * ...
413 *
414 * For threads scheduled under one of the normal scheduling policies
415 * (SCHED_OTHER, SCHED_IDLE, SCHED_BATCH), sched_priority is not used in
416 * scheduling decisions (it must be specified as 0).
417 *
418 * So - bottom line - this is a complete waste of time.
419 */
420 int priorityMin;
421 int priorityMax;
422 int schedulingPolicy{}; // on Linux, this appears to always be 0 - SCHED_OTHER, so cannot set priorities
423 {
424 sched_param param{};
425 Verify (::pthread_getschedparam (nh, &schedulingPolicy, &param) == 0);
426 priorityMin = ::sched_get_priority_min (schedulingPolicy);
427 priorityMax = ::sched_get_priority_max (schedulingPolicy);
428#if USE_NOISY_TRACE_IN_THIS_MODULE_
429 DbgTrace ("schedulingPolicy={}, default-priority={}, sPriorityMin_={}, priorityMax={}"_f, schedulingPolicy,
430 param.sched_priority, priorityMin, priorityMax);
431#endif
432 }
433 int newPThreadPriority{priorityMin};
434 switch (priority) {
435 case Priority::eLowest:
436 newPThreadPriority = priorityMin;
437 break;
438 case Priority::eBelowNormal:
439 newPThreadPriority = (priorityMax - priorityMin) * .25 + priorityMin;
440 break;
441 case Priority::eNormal:
442 newPThreadPriority = (priorityMax - priorityMin) * .5 + priorityMin;
443 break;
444 case Priority::eAboveNormal:
445 newPThreadPriority = (priorityMax - priorityMin) * .75 + priorityMin;
446 break;
447 case Priority::eHighest:
448 newPThreadPriority = priorityMax;
449 break;
450 default:
452 newPThreadPriority = (priorityMax - priorityMin) * .5 + priorityMin;
453 }
454#if USE_NOISY_TRACE_IN_THIS_MODULE_
455 DbgTrace ("Setting os thread priority for thread %{} to %{}"_f, (long long int)(nh), newPThreadPriority);
456#endif
457 /*
458 * \note Slightly simpler to use POSIX pthread_setschedprio - http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_setschedprio.html
459 * but alas MacOSX (XCode 10) doesn't support this, so keep more common code, and use about the same process - pthread_setschedparam
460 */
461 sched_param sp{};
462 sp.sched_priority = newPThreadPriority;
463 Verify (::pthread_setschedparam (nh, schedulingPolicy, &sp) == 0 or errno == EPERM);
464#else
465 // Cannot find any way todo this
467#endif
468 }
469}
470
471void Thread::Ptr::Rep_::ThreadMain_ (const shared_ptr<Rep_> thisThreadRep) noexcept
472{
473 RequireNotNull (thisThreadRep); // NOTE - since shared_ptr<> is NOT a const reference, this holds the bumped reference count til the end of ThreadMain_ scope
474 TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("Thread::Ptr::Rep_::ThreadMain_", "thisThreadRep={}"_f,
475 Characters::ToString (thisThreadRep))};
476#if qStroika_Foundation_Debug_AssertionsChecked
477 Require (Debug::AppearsDuringMainLifetime ());
478 [[maybe_unused]] auto&& cleanupCheckMain = Finally ([] () noexcept { Require (Debug::AppearsDuringMainLifetime ()); });
479#endif
480
481 try {
482 {
483 SuppressInterruptionInContext suppressInterruptionsOfThisThreadCallerKnowsWeHaveItBumpedAndCanProceed;
484 // This thread (ThreadMain) cannot possibly get interrupted BEFORE this - because only after this fRefCountBumpedInsideThreadMainEvent_ does the rest of the APP know about our thread ID
485 // baring an external process sending us a bogus signal)
486 //
487 // Note that BOTH the fRefCountBumpedInsideThreadMainEvent_ and the fStartReadyToTransitionToRunningEvent_ wait MUST come inside the try/catch for
488 thisThreadRep->fRefCountBumpedInsideThreadMainEvent_.Set ();
489 }
490
491 // So inside 'Run' - we will have access to this thread_local variable
492#if qCompilerAndStdLib_thread_local_static_inline_twice_Buggy
493 sCurrentThreadRep_BWA_ () = thisThreadRep;
494#else
495 sCurrentThreadRep_ = thisThreadRep;
496#endif
497
498 [[maybe_unused]] IDType thisThreadID = GetCurrentThreadID (); // NOTE - CANNOT call thisThreadRep->GetID () or in any way touch thisThreadRep->fThread_
499
500#if qStroika_Foundation_Execution_Thread_SupportThreadStatistics
501 {
502 Require (Debug::AppearsDuringMainLifetime ());
503 [[maybe_unused]] lock_guard critSec{sThreadSupportStatsMutex_};
504#if qStroika_Foundation_Debug_ShowThreadIndex
505 DbgTrace (
506 "Adding thread index {} to sRunningThreads_ ({})"_f, IndexRegistrar::sThe.GetIndex (thisThreadID),
507 Traversal::Iterable<IDType>{sRunningThreads_}.Map<vector<int>> ([] (IDType i) { return IndexRegistrar::sThe.GetIndex (i); }));
508#else
509 DbgTrace ("Adding thread id {} to sRunningThreads_ ({})"_f, thisThreadID, sRunningThreads_);
510#endif
511 Verify (sRunningThreads_.insert (thisThreadID).second); // .second true if inserted, so checking not already there
512 }
513 [[maybe_unused]] auto&& cleanup = Finally ([thisThreadID] () noexcept {
514 SuppressInterruptionInContext suppressThreadInterrupts; // may not be needed, but safer/harmless
515 Require (Debug::AppearsDuringMainLifetime ()); // Note: A crash in this code is FREQUENTLY the result of an attempt to destroy a thread after existing main () has started
516 [[maybe_unused]] lock_guard critSec{sThreadSupportStatsMutex_};
517#if qStroika_Foundation_Debug_ShowThreadIndex
518 DbgTrace (
519 "removing thread index {} from sRunningThreads_ ({})"_f, IndexRegistrar::sThe.GetIndex (thisThreadID),
520 Traversal::Iterable<IDType>{sRunningThreads_}.Map<vector<int>> ([] (IDType i) { return IndexRegistrar::sThe.GetIndex (i); }));
521#else
522 DbgTrace ("removing thread id {} from sRunningThreads_ ({})"_f, thisThreadID, sRunningThreads_);
523#endif
524 Verify (sRunningThreads_.erase (thisThreadID) == 1); // verify exactly one erased
525 });
526#endif
527
528 try {
529#if qStroika_Foundation_Common_Platform_POSIX
530 {
531 // we inherit blocked abort signal given how we are created in DoCreate() - so unblock it -
532 // and accept aborts after we've marked reference count as set.
533 sigset_t mySet;
534 sigemptyset (&mySet); // nb: cannot use :: cuz crapple uses macro --LGP 2016-12-31
535 (void)sigaddset (&mySet, SignalUsedForThreadInterrupt ()); // ""
536 Verify (::pthread_sigmask (SIG_UNBLOCK, &mySet, nullptr) == 0);
537#if USE_NOISY_TRACE_IN_THIS_MODULE_
538 DbgTrace ("Just set SIG_UNBLOCK for signal {} in this thread"_f, SignalToName (SignalUsedForThreadInterrupt ()));
539#endif
540 }
541#endif
542 thisThreadRep->fStartReadyToTransitionToRunningEvent_.Wait ();
543
544 [[maybe_unused]] auto&& cleanupThreadDoneEventSetter = Finally ([thisThreadRep] () noexcept {
545 // whether aborted before we transition state to running, or after, be sure to set this so we can 'join' the thread (also done in catch handlers)
546 thisThreadRep->fThreadDoneAndCanJoin_.Set ();
547 });
548
549 Assert (thisThreadID == thisThreadRep->GetID ()); // By now we know thisThreadRep->fThread_ has been assigned so it can be accessed
550
551 bool doRun = not thisThreadRep->fAbortRequested_ and not thisThreadRep->IsDone_ ();
552
553#if __cpp_lib_jthread >= 201911
554 // If a caller uses the std stop_token mechanism, assure the thread is marked as stopped/aborted
555 // But only register this after fRefCountBumpedInsideThreadMainEvent_ (would need to think more carefully to place this earlier)
556 // --LGP 2023-10-03
557 stop_callback stopCallback{thisThreadRep->fStopToken_, [=] () {
558 if (doRun) {
559 DbgTrace ("Something triggered stop_token request stop, so doing abort to make sure we are in an aborting (flag) state."_f);
560 // Abort () call is is slightly overkill, since frequently already in the aborting state, so check first
561 if (not thisThreadRep->fAbortRequested_) [[unlikely]] {
562 IgnoreExceptionsForCall (Ptr{thisThreadRep}.Abort ());
563 }
564 }
565 }};
566#endif
567 if (doRun) {
568 DbgTrace ("In Thread::Rep_::ThreadMain_ - set state to RUNNING for thread: {}"_f, thisThreadRep->ToString ());
569 thisThreadRep->Run_ ();
570 DbgTrace ("In Thread::Rep_::ThreadProc_ - setting state to COMPLETED for thread: {}"_f, thisThreadRep->ToString ());
571 }
572 }
573 catch (const AbortException&) {
574 SuppressInterruptionInContext suppressCtx;
575 DbgTrace ("In Thread::Rep_::ThreadProc_ - setting state to COMPLETED (InterruptException) for thread: {}"_f, thisThreadRep->ToString ());
576 thisThreadRep->fThreadDoneAndCanJoin_.Set ();
577 }
578 catch (...) {
579 SuppressInterruptionInContext suppressCtx;
580 DbgTrace ("In Thread::Rep_::ThreadProc_ - setting state to COMPLETED (due to EXCEPTION) for thread: {}"_f, thisThreadRep->ToString ());
581 thisThreadRep->fThreadDoneAndCanJoin_.Set ();
582 }
583 }
584 catch (const AbortException&) {
585 DbgTrace ("SERIOUS ERROR in Thread::Rep_::ThreadMain_ () - uncaught InterruptException - see sigsetmask stuff above - somehow not "
586 "working???"_f);
587 AssertNotReached (); // This should never happen - but if it does - better a trace message in a tracelog than 'unexpected' being called (with no way out)
588 }
589 catch (...) {
590 DbgTrace ("SERIOUS ERROR in Thread::Rep_::ThreadMain_ () - uncaught exception"_f);
591 AssertNotReached (); // This should never happen - but if it does - better a trace message in a tracelog than 'unexpected' being called (with no way out)
592 }
593}
594
595void Thread::Ptr::Rep_::NotifyOfInterruptionFromAnyThread_ ()
596{
597 // NOTE - SAW not IsDone_ FAIL ONCE - 2024-09-27 - MacOS - github - https://github.com/SophistSolutions/Stroika/actions/runs/11062067662/job/30735869523
598 // and https://github.com/SophistSolutions/Stroika/actions/runs/11230328605/job/31217486475 MACOS ONLY - 2024-10-08
599 // and https://github.com/SophistSolutions/Stroika/actions/runs/12872068258/job/35888537430 MACOS ONLY - 2025-01-20
600 // and https://github.com/SophistSolutions/Stroika/actions/runs/15288729158/job/43004184991 MACOS ONLY - 2025-05-28
601 Require (not IsDone_ ());
602
603 Require (fAbortRequested_);
604 //TraceContextBumper ctx{"Thread::Rep_::NotifyOfAbortFromAnyThread_"};
605
606 // typically abort from another thread, but in principle this could happen!
607 if (GetCurrentThreadID () == GetID ()) [[unlikely]] {
608 CheckForInterruption (); // unless suppressed, this will throw
609 }
610
611 // Note we fall through here either if we have throws suppressed, or if sending to another thread
612
613 // if fThreadValid_, and can try to cancel it; else start () process early enuf it will cancel
614 if (fThreadValid_) {
615 /*
616 * Do some platform-specific magic to terminate ongoing system calls
617 *
618 * On POSIX - this is sending a signal which generates EINTR error.
619 * On Windoze - this is QueueUserAPC to enter an alertable state.
620 */
621#if qStroika_Foundation_Common_Platform_POSIX
622 {
623 [[maybe_unused]] lock_guard critSec{sHandlerInstalled_};
624 if (not sHandlerInstalled_) {
625 SignalHandlerRegistry::Get ().AddSignalHandler (SignalUsedForThreadInterrupt (), kCallInRepThreadAbortProcSignalHandler_);
626 sHandlerInstalled_ = true;
627 }
628 }
629 (void)SendSignal (GetNativeHandle (), SignalUsedForThreadInterrupt ());
630#elif qStroika_Foundation_Common_Platform_Windows
631 Verify (::QueueUserAPC (&CalledInRepThreadAbortProc_, GetNativeHandle (), reinterpret_cast<ULONG_PTR> (this)));
632#endif
633 }
634}
635
636#if qStroika_Foundation_Common_Platform_POSIX
637void Thread::Ptr::Rep_::InterruptionSignalHandler_ (SignalID signal) noexcept
638{
639 //
640 //#if USE_NOISY_TRACE_IN_THIS_MODULE_
641 // unsafe to call trace code - because called as unsafe (SignalHandler::Type::eDirect) handler
642 //TraceContextBumper ctx{"Thread::Ptr::Rep_::InterruptionSignalHandler_"};
643 //#endif
644 // This doesn't REALLY need to get called. Its enough to have the side-effect of the EINTR from system calls.
645 // the TLS variable gets set through the rep pointer in NotifyOfInterruptionFromAnyThread_
646 //
647 // Note - using SIG_IGN doesn't work, because then the signal doesn't get delivered, and the EINTR doesn't happen
648 //
649}
650#elif qStroika_Foundation_Common_Platform_Windows
651void CALLBACK Thread::Ptr::Rep_::CalledInRepThreadAbortProc_ (ULONG_PTR lpParameter)
652{
653 TraceContextBumper ctx{"Thread::Ptr::Rep_::CalledInRepThreadAbortProc_"};
654 [[maybe_unused]] Ptr::Rep_* rep = reinterpret_cast<Ptr::Rep_*> (lpParameter);
655 Require (GetCurrentThreadID () == rep->GetID ());
656 if (rep->fThrowInterruptExceptionInsideUserAPC_) [[unlikely]] {
658 }
659}
660#endif
661
662/*
663 ********************************************************************************
664 ******************************** Thread::Ptr ***********************************
665 ********************************************************************************
666 */
667namespace {
668 Synchronized<Thread::Configuration> sDefaultConfiguration_;
669}
670
671namespace {
672 Thread::Configuration CombineCFGs_ (const optional<Thread::Configuration>& cfg)
673 {
675 if (cfg) {
676 if (cfg->fStackSize) {
677 result.fStackSize = *cfg->fStackSize;
678 }
679 if (cfg->fStackGuard) {
680 result.fStackSize = *cfg->fStackGuard;
681 }
682#if qStroika_Foundation_Common_Platform_Windows
683 if (cfg->fThrowInterruptExceptionInsideUserAPC) {
684 result.fThrowInterruptExceptionInsideUserAPC = *cfg->fThrowInterruptExceptionInsideUserAPC;
685 }
686#endif
687 }
688 return result;
689 }
690}
691
693{
694 RequireNotNull (fRep_);
695 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_}; // smart ptr - its the ptr thats const, not the rep
697 if (nh == NativeHandleType{}) {
698 // This can happen if you set the thread priority before starting the thread (actually probably a common sequence of events)
699 fRep_->fInitialPriority_.store (priority);
700 return;
701 }
702 else {
703 fRep_->ApplyPriority (priority);
704 }
705}
706
708{
709 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
710 return fRep_ == nullptr ? String{} : fRep_->fThreadName_;
711}
712
713void Thread::Ptr::SetThreadName (const String& threadName) const
714{
715 RequireNotNull (fRep_);
716 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_}; // smart ptr - its the ptr thats const, not the rep
717#if USE_NOISY_TRACE_IN_THIS_MODULE_
718 TraceContextBumper ctx{"Thread::SetThreadName", "thisThreadID={}, threadName = '{}'"_f, GetID (), threadName};
719#endif
720 if (fRep_->fThreadName_ != threadName) {
721 fRep_->fThreadName_ = threadName.As<wstring> ();
722 fRep_->ApplyThreadName2OSThreadObject ();
723 }
724}
725
727{
728 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
729 return fRep_ == nullptr ? "nullptr"sv : fRep_->ToString ();
730}
731
733{
734 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_}; // smart ptr - its the ptr thats const, not the rep
735 Debug::TraceContextBumper ctx{"Thread::Start", "*this={}"_f, ToString ()};
736 RequireNotNull (fRep_);
737 Require (not fRep_->fStartEverInitiated_);
738#if qStroika_Foundation_Debug_AssertionsChecked
739 {
740 auto s = GetStatus (); // @todo - consider - not sure about this
741 Require (s == Status::eNotYetRunning or s == Status::eAborting); // if works - document
742 }
743#endif
744
745 /*
746 * Stroika thread-start choreography:
747 *
748 * CALLING_THREAD | STATE CREATED THREAD
749 * Create 'shared_ptr<Rep>' <| eNotYetRunning
750 * (notes - anytime after state set to eNotYetRunning can transition to eAborting. Only from eRunning or eAborting can it transition to eCompleted )
751 * ENTER CALL TO Thread::Ptr::Start () <|
752 * START SuppressInterruptionInContext <|
753 * Create jthread object, giving it <|
754 * a 'ThreadMain' <|
755 * WAIT ON fRefCountBumpedInsideThreadMainEvent_ <|> ENTER Rep_::ThreadMain_ with BUMPED shared_ptr<Rep> refcount
756 * |> thisThreadRep->fRefCountBumpedInsideThreadMainEvent_.Set () (NOTE thisThreadRep == fRefCountBumpedInsideThreadMainEvent_)
757 * END SuppressInterruptionInContext |
758 * Setup a few thread properties, name, priority <|> Setup thread-local properties, inside ThreadMain/new thread
759 * fRep_->fStartReadyToTransitionToRunningEvent_.Set ()<|> thisThreadRep->fStartReadyToTransitionToRunningEvent_.Wait (); -- NOTE CANNOT ACCESS thisThreadRep->fThread_ until this point
760 * return/done <|> eRunning|(etc) STATE TRANSITION TO RUNNING (MAYBE - COULD HAVE BEEN ALREADY ABORTED)
761 */
762
763 {
764 /*
765 * Once we have constructed the other thread, its critical it be allowed to run at least to the
766 * point where it's bumped its reference count before we allow aborting this thread.
767 */
768 SuppressInterruptionInContext suppressInterruptionsOfThisThreadWhileConstructingRepOtherElseLoseSharedPtrEtc;
769
770 fRep_->fStartEverInitiated_ = true; //atomic/publish
771 if (fRep_->fAbortRequested_) [[unlikely]] {
772 Throw (RuntimeErrorException{"Thread aborted during start"sv}); // check and if aborting now, don't go further
773 }
774
775#if __cpp_lib_jthread >= 201911
776 fRep_->fStopToken_ = fRep_->fStopSource_.get_token ();
777 fRep_->fThread_ = jthread{[this] () -> void { Rep_::ThreadMain_ (fRep_); }};
778#else
779 fRep_->fThread_ = thread{[this] () -> void { Rep_::ThreadMain_ (fRep_); }};
780#endif
781 fRep_->fThreadValid_ = true;
782
783 // assure we wait for this, so we don't ever let refcount go to zero before the thread has started.
784 fRep_->fRefCountBumpedInsideThreadMainEvent_.Wait ();
785
786 // Once we've gotten here, the ThreadMain is executing, but 'paused' waiting for us to setup more stuff
787 }
788
789 // Setup a few thread properties, name, priority
790 fRep_->ApplyThreadName2OSThreadObject ();
791 if (optional<Priority> p = fRep_->fInitialPriority_.load ()) {
792 fRep_->ApplyPriority (*p);
793 }
794 DbgTrace ("Requesting transition to running for {}"_f, ToString ());
795 fRep_->fStartReadyToTransitionToRunningEvent_.Set ();
796}
797void Thread::Ptr::Start (WaitUntilStarted) const
798{
799 Start ();
800 for (auto s = GetStatus (); s != Status::eNotYetRunning; s = GetStatus ()) {
801 // @todo fix this logic - set explicit when we do the SET EVENT above (before). But then need to change the threadmain logic to accommodate;
802 // low priority since this overload probably not used...
803 // --LGP 2023-11-30
804 this_thread::yield ();
805 }
806#if qStroika_Foundation_Debug_AssertionsChecked
807 auto s = GetStatus ();
808 Ensure (s == Status::eRunning or s == Status::eAborting or s == Status::eCompleted);
809#endif
810}
811
813{
815 Require (*this != nullptr);
816 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_}; // smart ptr - its the ptr thats const, not the rep
817
818#if __cpp_lib_jthread >= 201911
819 bool wasAborted = fRep_->fAbortRequested_;
820#endif
821 // Abort can be called with status in ANY state, except nullptr (which would mean ever assigned Thread::New());
822 fRep_->fAbortRequested_ = true;
823 if (fRep_->fStartEverInitiated_) {
824#if __cpp_lib_jthread >= 201911
825 // If transitioning to aborted state, notify any existing stop_callbacks
826 // not needed to check prevState - since https://en.cppreference.com/w/cpp/thread/jthread/request_stop says requst_stop checks if already requested.
827 if (not wasAborted) [[likely]] {
828 DbgTrace ("Transitioned state to aborting, so calling fThread_.get_stop_source ().request_stop ();"_f);
829 fRep_->fStopSource_.request_stop ();
830 }
831#endif
832 }
833 else {
834 /*
835 * Then mark the thread as completed.
836 *
837 * If we have not yet called start (or gotten to the point where fStartEverInitiated_ gets set), then set the CanJoin event.
838 * sb safe.
839 */
840 fRep_->fThreadDoneAndCanJoin_.Set ();
841 }
842 if (not IsDone ()) [[likely]] {
843 // by default - tries to trigger a throw-abort-exception in the right thread using UNIX signals or QueueUserAPC ()
844 fRep_->NotifyOfInterruptionFromAnyThread_ ();
845 }
846#if USE_NOISY_TRACE_IN_THIS_MODULE_
847 DbgTrace ("leaving *this = {}"_f, *this);
848#endif
849}
850
852{
853 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("Thread::AbortAndWaitForDoneUntil",
854 "*this={}, timeoutAt={}"_f, ToString (), timeoutAt)};
855 RequireNotNull (*this);
856 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
857
858 Abort ();
859 WaitForDoneUntil (timeoutAt);
860}
861
863{
864#if USE_NOISY_TRACE_IN_THIS_MODULE_
865 Debug::TraceContextBumper ctx{"Thread::ThrowIfDoneWithException", "*this={}"_f, *this};
866#endif
867 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
868 if (fRep_ and fRep_->IsDone_ () and fRep_->fSavedException_.load () != nullptr) {
869 // safe not holding lock cuz code simpler, and cannot transition from savedExcept to none - never cleared
870 ReThrow (fRep_->fSavedException_.load (), "Rethrowing exception across threads");
871 }
872}
873
875{
876 Debug::TraceContextBumper ctx{"Thread::WaitForDoneUntil", "*this={}, timeoutAt={}"_f, ToString (), timeoutAt};
877 if (not WaitForDoneUntilQuietly (timeoutAt)) {
879 }
880}
881
883{
884#if USE_NOISY_TRACE_IN_THIS_MODULE_
886 "*this={}, timeoutAt={}"_f, ToString (), timeoutAt)};
887#endif
888 Require (*this != nullptr);
889 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
890 CheckForInterruption (); // always a cancelation point
891 if (fRep_->fThreadDoneAndCanJoin_.WaitUntilQuietly (timeoutAt) == WaitableEvent::WaitStatus::eTriggered) {
892 /*
893 * This is not critical, but has the effect of assuring the COUNT of existing threads is what the caller would expect.
894 * This really only has effect #if qStroika_Foundation_Execution_Thread_SupportThreadStatistics
895 * because that's the only time we have an important side effect of the threads finalizing.
896 *
897 * @see http://stroika-bugs.sophists.com/browse/STK-496
898 *
899 * NOTE: because we call this join () inside fAccessSTDThreadMutex_, its critical the running thread has terminated to the point where it will no
900 * longer access fThread_ (and therefore not lock fAccessSTDThreadMutex_)
901 */
902 if (fRep_->fThreadValid_ and fRep_->fThread_.joinable ()) {
903 // fThread_.join () will block indefinitely - but since we waited on fRep_->fThreadDoneAndCanJoin_ - it shouldn't really take long
904 fRep_->fThread_.join ();
905 }
906 return true;
907 }
908 Assert (timeoutAt <= Time::GetTickCount ()); // otherwise we couldn't have timed out
909 return false;
910}
911
912#if qStroika_Foundation_Common_Platform_Windows
913void Thread::Ptr::WaitForDoneWhilePumpingMessages (Time::DurationSeconds timeout) const
914{
915 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
916 Require (*this != nullptr);
918 HANDLE thread = fRep_->GetNativeHandle ();
919 if (thread == INVALID_HANDLE_VALUE) {
920 return;
921 }
922 Time::TimePointSeconds timeoutAt = Time::GetTickCount () + timeout;
923 // CRUDDY impl - but decent enuf for first draft
924 while (GetStatus () != Status::eCompleted) {
925 Time::DurationSeconds time2Wait = timeoutAt - Time::GetTickCount ();
926 if (time2Wait <= 0s) {
928 }
929 Platform::Windows::WaitAndPumpMessages (nullptr, {thread}, time2Wait);
930 }
931 WaitForDone (); // just to get the qStroika_Foundation_Execution_Thread_SupportThreadStatistics / join ()
932}
933#endif
934
935/*
936 ********************************************************************************
937 **************************** Thread::CleanupPtr ********************************
938 ********************************************************************************
939 */
940Thread::CleanupPtr::~CleanupPtr ()
941{
942 if (*this != nullptr) {
943 SuppressInterruptionInContext suppressInterruption;
944 if (fAbort_) {
946 }
947 else {
948 WaitForDone ();
949 }
950 }
951}
952
953/*
954 ********************************************************************************
955 *********************************** Thread *************************************
956 ********************************************************************************
957 */
958Thread::Ptr Thread::New (const function<void ()>& fun2CallOnce, const optional<Characters::String>& name, const optional<Configuration>& configuration)
959{
960 // All Thread::New () overloads vector through this one...
961 Ptr ptr = Ptr{make_shared<Ptr::Rep_> (fun2CallOnce, CombineCFGs_ (configuration))};
962 if (name) {
963 ptr.SetThreadName (*name);
964 }
965 return ptr;
966}
967
969{
970 return sDefaultConfiguration_.load ();
971}
972
973Thread::Configuration Thread::DefaultConfiguration (const optional<Configuration>& newConfiguration)
974{
975 auto result = sDefaultConfiguration_.load ();
976 if (newConfiguration) {
977 sDefaultConfiguration_.store (newConfiguration.value ());
978 }
979 return result;
980}
981
982#if qStroika_Foundation_Execution_Thread_SupportThreadStatistics
983Thread::Statistics Thread::GetStatistics ()
984{
985 [[maybe_unused]] lock_guard critSec{sThreadSupportStatsMutex_};
986 return Statistics{Containers::Set<IDType>{sRunningThreads_}};
987}
988#endif
989
991{
992#if USE_NOISY_TRACE_IN_THIS_MODULE_
993 Debug::TraceContextBumper ctx{"Thread::Abort", "threads={}"_f, threads};
994#endif
995 threads.Apply ([] (Ptr t) { t.Abort (); });
996}
997
999{
1000#if USE_NOISY_TRACE_IN_THIS_MODULE_
1001 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("Thread::AbortAndWaitForDoneUntil",
1002 "threads={}, timeoutAt={}"_f, threads, timeoutAt)};
1003#endif
1004 /*
1005 * Before Stroika v3, we would sometimes re-send the abort message, but no need if this is not buggy. One abort sb enuf.
1006 */
1007 Abort (threads);
1008 WaitForDoneUntil (threads, timeoutAt);
1009}
1010
1012{
1013#if USE_NOISY_TRACE_IN_THIS_MODULE_
1014 Debug::TraceContextBumper ctx{"Thread::WaitForDoneUntil", "threads={}, timeoutAt={}"_f, threads, timeoutAt};
1015#endif
1016 CheckForInterruption (); // always a cancelation point (even if empty list)
1017 // consider rewriting so we don't do this sequentially, but 'harvest' the ones completed (much as we did in Stroika v2.1), but perhaps no point.
1018 // This is probably fine.
1019 threads.Apply ([timeoutAt] (const Ptr& t) { t.WaitForDoneUntil (timeoutAt); });
1020}
1021
1022#if qStroika_Foundation_Common_Platform_POSIX
1023namespace {
1024 SignalID sSignalUsedForThreadInterrupt_ = SIGUSR2;
1025}
1026SignalID Thread::SignalUsedForThreadInterrupt () noexcept
1027{
1028 return sSignalUsedForThreadInterrupt_;
1029}
1030SignalID Thread::SignalUsedForThreadInterrupt (optional<SignalID> signalNumber)
1031{
1032 SignalID result = sSignalUsedForThreadInterrupt_;
1033 if (signalNumber) {
1034 [[maybe_unused]] lock_guard critSec{sHandlerInstalled_};
1035 if (sHandlerInstalled_) {
1036 SignalHandlerRegistry::Get ().RemoveSignalHandler (SignalUsedForThreadInterrupt (), kCallInRepThreadAbortProcSignalHandler_);
1037 sHandlerInstalled_ = false;
1038 }
1039 sSignalUsedForThreadInterrupt_ = signalNumber.value ();
1040 // install new handler
1041 if (not sHandlerInstalled_) {
1042 SignalHandlerRegistry::Get ().AddSignalHandler (SignalUsedForThreadInterrupt (), kCallInRepThreadAbortProcSignalHandler_);
1043 sHandlerInstalled_ = true;
1044 }
1045 }
1046 return result;
1047}
1048#endif
1049
1050/*
1051 ********************************************************************************
1052 **************************** Thread::FormatThreadID ****************************
1053 ********************************************************************************
1054 */
1055wstring Thread::FormatThreadID (Thread::IDType threadID, const FormatThreadInfo& formatThreadInfo)
1056{
1057 return String::FromNarrowSDKString (FormatThreadID_A (threadID, formatThreadInfo)).As<wstring> ();
1058}
1059
1060string Thread::FormatThreadID_A (Thread::IDType threadID, const FormatThreadInfo& formatThreadInfo)
1061{
1063
1064 /*
1065 * stdc++ doesn't define a way to get the INT thread id, just a string. But they don't format it the
1066 * way we usually format a thread ID (hex, fixed width). So do that, so thread IDs look more consistent.
1067 */
1068 stringstream out;
1069 out << threadID;
1070
1071#if qStroika_Foundation_Common_Platform_Windows
1072 constexpr size_t kSizeOfThreadID_ = sizeof (DWORD); // All MSFT SDK Thread APIs use DWORD for thread id
1073#elif qStroika_Foundation_Common_Platform_POSIX
1074 constexpr size_t kSizeOfThreadID_ = sizeof (pthread_t);
1075#else
1076 // on MSFT this object is much larger than thread id because it includes handle and id
1077 // Not a reliable measure anywhere, but probably our best guess
1078 constexpr size_t kSizeOfThreadID_ = sizeof (Thread::IDType);
1079#endif
1080
1081 if constexpr (kSizeOfThreadID_ >= sizeof (uint64_t)) {
1082 uint64_t threadIDInt = 0;
1083 out >> threadIDInt;
1084 return formatThreadInfo.fIncludeLeadingZeros ? Characters::CString::Format ("0x%016llx", threadIDInt)
1085 : Characters::CString::Format ("0x%llx", threadIDInt);
1086 }
1087 else {
1088 uint32_t threadIDInt = 0;
1089 out >> threadIDInt;
1090 /*
1091 * Often, it appears ThreadIDs IDs are < 16bits, so making the printout format shorter makes it a bit more readable.
1092 *
1093 * However, I don't see any reliable way to tell this is the case, so don't bother for now. A trouble with checking on
1094 * a per-thread-id basis is that often the MAIN THREAD is 0, which is < 0xffff. Then we get one size and then
1095 * on the rest a different size, so the layout in the debug trace log looks funny.
1096 */
1097 constexpr bool kUse16BitThreadIDsIfTheyFit_{false};
1098 const bool kUse16Bit_ = kUse16BitThreadIDsIfTheyFit_ and threadIDInt <= 0xffff;
1099 if (kUse16Bit_) {
1100 return formatThreadInfo.fIncludeLeadingZeros ? Characters::CString::Format ("0x%04x", threadIDInt)
1101 : Characters::CString::Format ("0x%x", threadIDInt);
1102 }
1103 else {
1104 return formatThreadInfo.fIncludeLeadingZeros ? Characters::CString::Format ("0x%08x", threadIDInt)
1105 : Characters::CString::Format ("0x%x", threadIDInt);
1106 }
1107 }
1108}
1109
1110#if qCompilerAndStdLib_ThreadLocalInlineDupSymbol_Buggy
1111#if __cpp_lib_jthread >= 201911
1112/*
1113 ********************************************************************************
1114 ************************ Thread::GetCurrentThreadStopToken *********************
1115 ********************************************************************************
1116 */
1117optional<stop_token> Thread::GetCurrentThreadStopToken ()
1118{
1119 if (Ptr curThread = GetCurrent ()) {
1120 return curThread.GetStopToken ();
1121 }
1122 else {
1123 return nullopt;
1124 }
1125}
1126#endif
1127#endif
1128
1129#if qCompilerAndStdLib_ThreadLocalInlineDupSymbol_Buggy
1130/*
1131 ********************************************************************************
1132 ******************* Thread::IsCurrentThreadInterruptible ***********************
1133 ********************************************************************************
1134 */
1136{
1137 return GetCurrent () != nullptr;
1138}
1139#endif
1140
1141/*
1142 ********************************************************************************
1143 *************************** Thread::CheckForInterruption ***********************
1144 ********************************************************************************
1145 */
1147{
1148 /*
1149 * NOTE: subtle but important that we use static Thread::InterruptException::kThe so we avoid
1150 * re-throw with string operations. Otherwise we would have to use SuppressInterruptionInContext
1151 * just before the actual throw.
1152 */
1153 if (shared_ptr<Ptr::Rep_> thisRunningThreadRep =
1154#if qCompilerAndStdLib_thread_local_static_inline_twice_Buggy
1155 Ptr::sCurrentThreadRep_BWA_ ().lock ()
1156#else
1157 Ptr::sCurrentThreadRep_.lock ()
1158#endif
1159 ) {
1160 if (t_InterruptionSuppressDepth_ == 0) [[likely]] {
1161 if (thisRunningThreadRep->fAbortRequested_) [[unlikely]] {
1162 Throw (Thread::AbortException::kThe);
1163 }
1164 }
1165#if qStroika_Foundation_Debug_DefaultTracingOn
1166 else if (thisRunningThreadRep->fAbortRequested_) {
1167 static atomic<unsigned int> sSuperSuppress_{};
1168 if (++sSuperSuppress_ <= 1) {
1169 IgnoreExceptionsForCall (DbgTrace ("Suppressed interrupt throw: t_InterruptionSuppressDepth_={}, t_Interrupting_={}"_f,
1170 t_InterruptionSuppressDepth_, thisRunningThreadRep->fAbortRequested_.load ()));
1171 sSuperSuppress_--;
1172 }
1173 }
1174#endif
1175 }
1176}
1177
1178/*
1179 ********************************************************************************
1180 ********************************** Thread::Yield *******************************
1181 ********************************************************************************
1182 */
1184{
1185 /*
1186 * Check before so we abort more quickly, and after since while we were sleeping we could be interrupted.
1187 * And this (yield) happens at a non-time critical point, so though checking before and after is redundant,
1188 * not importantly
1189 */
1191 this_thread::yield ();
1193}
#define RequireNotReached()
Definition Assertions.h:385
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
Definition Assertions.h:48
#define WeakAssertNotImplemented()
Definition Assertions.h:483
#define RequireNotNull(p)
Definition Assertions.h:347
#define AssertNotReached()
Definition Assertions.h:355
#define Verify(c)
Definition Assertions.h:419
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
#define DbgTrace
Definition Trace.h:309
#define qStroika_Foundation_Debug_ShowThreadIndex
if true, emit a much shorter thread ID, making - I suspect (testing) for terser and clearer tracelogs...
Definition Trace.h:83
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:270
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual string AsNarrowSDKString() const
Definition String.inl:834
static String FromNarrowSDKString(const char *from)
Definition String.inl:470
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
NOT a real mutex - just a debugging infrastructure support tool so in debug builds can be assured thr...
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Definition Exceptions.h:157
nonvirtual void AddSignalHandler(SignalID signal, const SignalHandler &handler)
nonvirtual void RemoveSignalHandler(SignalID signal, const SignalHandler &handler)
Wrap any object with Synchronized<> and it can be used similarly to the base type,...
Thread::Ptr is a (unsynchronized) smart pointer referencing an internally synchronized std::thread ob...
Definition Thread.h:334
nonvirtual void SetThreadName(const Characters::String &threadName) const
Definition Thread.cpp:713
nonvirtual void WaitForDoneUntil(Time::TimePointSeconds timeoutAt) const
Definition Thread.cpp:874
nonvirtual void Abort() const
Abort gracefully shuts down and terminates the given thread (using cooperative multitasking).
Definition Thread.cpp:812
nonvirtual bool WaitForDoneUntilQuietly(Time::TimePointSeconds timeoutAt) const
Definition Thread.cpp:882
nonvirtual void ThrowIfDoneWithException() const
Definition Thread.cpp:862
nonvirtual NativeHandleType GetNativeHandle() const noexcept
Definition Thread.inl:178
nonvirtual Characters::String ToString() const
Definition Thread.cpp:726
nonvirtual void AbortAndWaitForDoneUntil(Time::TimePointSeconds timeoutAt) const
Abort () the thread, and then WaitForDone () - but if doesn't finish fast enough, send extra aborts.
Definition Thread.cpp:851
nonvirtual Characters::String GetThreadName() const
Definition Thread.cpp:707
nonvirtual void SetThreadPriority(Priority priority=Priority::eNormal) const
Definition Thread.cpp:692
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.
nonvirtual RESULT_CONTAINER Map(ELEMENT_MAPPER &&elementMapper) const
functional API which iterates over all members of an Iterable, applies a map function to each element...
String ToString(T &&t, ARGS... args)
Return a debug-friendly, display version of the argument: not guaranteed parsable or usable except fo...
Definition ToString.inl:465
void WaitAndPumpMessages(HWND dialog=nullptr, Time::DurationSeconds forNSecs=0.1s)
Ptr New(const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
Definition Thread.cpp:958
void AbortAndWaitForDoneUntil(const Traversal::Iterable< Ptr > &threads, Time::TimePointSeconds timeoutAt)
Definition Thread.cpp:998
thread::native_handle_type NativeHandleType
Definition Thread.h:222
void Start(const Traversal::Iterable< Ptr > &threads)
Definition Thread.inl:347
wstring FormatThreadID(Thread::IDType threadID, const FormatThreadInfo &formatInfo={})
Definition Thread.cpp:1055
void WaitForDone(const Traversal::Iterable< Ptr > &threads, Time::DurationSeconds timeout=Time::kInfinity)
Definition Thread.inl:351
dont_inline void Yield()
calls CheckForInterruption, and std::this_thread::yield ()
Definition Thread.cpp:1183
Configuration DefaultConfiguration() noexcept
Definition Thread.cpp:968
void WaitForDoneUntil(const Traversal::Iterable< Ptr > &threads, Time::TimePointSeconds timeoutAt)
Definition Thread.cpp:1011
void Abort(const Traversal::Iterable< Ptr > &threads)
foreach Thread t: t.Abort ()
Definition Thread.cpp:990
void AbortAndWaitForDone(const Traversal::Iterable< Ptr > &threads, Time::DurationSeconds timeout=Time::kInfinity)
shorthand for AbortAndWaitForDoneUntil (Time::GetTickCount () + timeout)
Definition Thread.inl:343
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
Definition Finally.inl:31
errno_t SendSignal(thread::native_handle_type target, SignalID signal)
EXPERIMENTAL SUPPORT FOR THREAD STACK (and maybe other) settings.
Definition Thread.h:231