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