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