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