Stroika Library 3.0d18
 
Loading...
Searching...
No Matches
Stroika::Foundation::Execution::Thread Namespace Reference

Thread is a namespace for Stroika thread code,. More...

Classes

class  CleanupPtr
 
struct  Configuration
 EXPERIMENTAL SUPPORT FOR THREAD STACK (and maybe other) settings. More...
 
class  IndexRegistrar
 
class  Ptr
 Thread::Ptr is a (unsynchronized) smart pointer referencing an internally synchronized std::thread object (rep), with special feautres, including cancelation. More...
 
class  SuppressInterruptionInContext
 

Typedefs

using IDType = thread::id
 
using NativeHandleType = thread::native_handle_type
 

Enumerations

enum  AutoStartFlag
 
enum class  Status : uint8_t
 
enum class  Priority
 

Functions

void CheckForInterruption ()
 
bool IsCurrentThreadInterruptible ()
 
Configuration DefaultConfiguration () noexcept
 
Ptr New (const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
 
void Start (const Traversal::Iterable< Ptr > &threads)
 
void Abort (const Traversal::Iterable< Ptr > &threads)
 foreach Thread t: t.Abort ()
 
void WaitForDone (const Traversal::Iterable< Ptr > &threads, Time::DurationSeconds timeout=Time::kInfinity)
 
void WaitForDoneUntil (const Traversal::Iterable< Ptr > &threads, Time::TimePointSeconds timeoutAt)
 
void AbortAndWaitForDone (const Traversal::Iterable< Ptr > &threads, Time::DurationSeconds timeout=Time::kInfinity)
 
 
void AbortAndWaitForDoneUntil (const Traversal::Iterable< Ptr > &threads, Time::TimePointSeconds timeoutAt)
 
wstring FormatThreadID (Thread::IDType threadID, const FormatThreadInfo &formatInfo={})
 
Ptr GetCurrent ()
 
dont_inline void Yield ()
 calls CheckForInterruption, and std::this_thread::yield ()
 

Detailed Description

Thread is a namespace for Stroika thread code,.

See also
Thread::Ptr or @ThreadNew
Stroika Threads are built on std::jthread, so can be used mostly interoperably. However,
Stroika threads add a number of very useful features to std::threads: o Simpler Cancelation/Interruption/Aborting (c++ 20 introduces thread cancelation via explicitly managed stop_tokens, but Stroika's thread cancelation uses this, but hides it - mostly). o EINTR handling (POSIX only)

as well as a couple modestly helpful features (that can be done other ways directly with std::thread): o Copyability (using Thread::Ptr) o Better lifetime management (the thread envelope - object you create - can go away, but the underlying thread can continue running, with its memory/resources being cleaned up autoamtically. o And several minor features like (mostly) portably setting thread priorities, names, etc and more

Note - the cancelation feature is very handy for building large scale applications which use worker tasks and things like thread pools, to be able to reclaim resources, cancel ongoing operations as useless, and maintain overall running system integrity.

Using the smartpointer wrapper Thread around a thread guarantees its reference counting will work safely - so that even when all external references go away, the fact that the thread is still running will keep the reference count non-zero.

Thread Aborting: The Stroika Thread class supports the idea of 'aborting' a thread (cooperatitve cancelation)

Thread Interruoption: Stroika v2.1 and earlier supported a feature like Thread-Abort, called 'Interrupt' - that acted like thread abort, except that it was not sticky - and once the interrupt was handled, it 'reset' to not interrupting anymore.

I never really found any compelling use case for this idea (borrowed from java thread interrupt).

And I think the scenarios where it MIGHT be helpful, could be handled just as easily with condition variables.

Also, c++20 added support which allows thead aborting (stop_token), but that doesn't work well/easily for basic interruption.

So - abandoned Thread::Interrupt(...) in Stroika v3.

Nomenclature Note: In some libraries, the term interruption, cancelation is used for thread aborting. > java uses interruption > boost uses cancelation, > POSIX uses cancelation (pthread_canel) > and .net uses Interrupt and Abort

The basic idea is that a thread goes off on its own, doing stuff, and an external force decides to tell it to stop.

Examples of this might be: (1) A search user interface, which starts searching as the user types. Once the process has received a certain number of characters it starts searching, but perhaps before the search is done, another character comes in, so the GUI code will want to Abort the existing search, and start a new one (with the extra character(s)). (2) A web server - which is serving up content, and it told to shut-down. It must interrupt existing in process processes - some maybe handling a read/write sequence, and some perhaps doing a socket listen/accept call.

When a thread is aborted, it (in that thread) throws class AbortException;

Thread 'interruption' happens via 'cancelation points'. Cancelation points are points in the code where we check to see if the current running thread has been interrupted (or aborted) and raise the appropriate exception.

This mechanism is almost completely co-operative, meaning that user code must call CheckForInterruption () in the right places to make interruption work. But Stroika is structured to make that happen automatically throughout most of its code, but having key routines (like Sleep, and WaitableEvents) automatically internally be cancelation points.

The only slight exception to this is on Windows, where we have the APC mechanism that will interupt a wide vareity of windows system calls (see docs below).

Note
- its important that this 'interruption' can only happen at well defined times, because that allows for safe and reliable cleanup of whatever activitites were being done (e.g. cannot interupt in a destructor)

Thread interruption/aborting is tricky todo safely and portably. We take a safe, cooperative approach. (1) We maintain a thread-local-storage variable - saying if this thread has been aborted. Sprinkling CheckForInterruption throughout your code - will trigger an InterupptedException () or AbortException () in that thread context. Note a pointer to that TLS interruption variable is also stored in the thread 'rep' object, so it can be set (by Thread::Interrupt).

(2) WINDOWS ONLY: Async-injection (QueueUserAPC/Windows) - Alertable state - certain (alertable) functions get interrupted and return WAIT_IO_COMPLETION (like POSIX EINTR).

See also
https://msdn.microsoft.com/en-us/library/windows/desktop/aa363772(v=vs.85).aspx o SleepEx o WaitForSingleObjectEx o WaitForMultipleObjectsEx o SignalObjectAndWait o MsgWaitForMultipleObjectsEx But also from https://msdn.microsoft.com/en-us/library/windows/desktop/ms741669(v=vs.85).aspx o WSAPoll ... Winsock performs an alertable, similarly for recvfrom, accept, etc. Not clear how to get full list of windows alertable functions...

This allows interrupting windows waiting / blocking functions (most of them).

(3) POSIX ONLY: Signal injection - we send a special (defaults to SIG_USR2) signal to a particular thread. This triggers an EINTR on most UNIX system calls, which are automatically restarted in most cases (

See also
Execution::Handle_ErrNoResultInterruption), but in case of interruption, we call CheckForInterruption ()

This allows interrupting UNIX waiting / blocking functions (all of them?).

Note
See also
defails on cancelation points, because many common std C++ blocking operations, like std::mutex are not std::condition_variable are not cancelation points and so can break this mechanism if not used carefully

Handle_ErrNoResultInterruption() The short of it is that you need to catch EINTR and restart the call for these system calls: o read, readv, write, writev, ioctl o open() when dealing with a fifo o wait* o Anything socket based (send*, recv*, connect, accept etc) o flock and lock control with fcntl o mq_ functions which can block o futex o sem_wait (and timed wait) o pause, sigsuspend, sigtimedwait, sigwaitinfo o poll, epoll_wait, select and 'p' versions of the same o msgrcv, msgsnd, semop, semtimedop o close (although, on Linux, EINTR won't happen here) o any sleep functions (careful, you need to handle this are restart with different arguments) This is integrated with Stroika's thread cancelation mechanism.

See also
Handle_ErrNoResultInterruption

Thread Cancelation Points A cancelation point is any (typically but not always blocking) function which will be interrupted (cause interruption exception) and stop blocking, when someone calls Thread::Interupt or Thread::Abort() on its thread object.

Roughly, these are subroutines which call CheckForInterruption () frequently, internally.

As its crucial to understand this in the API, we document each such function with Cancelation Point in its doc header. For example, the Sleep() overloads are cancelation points.

Equally important to understand, is when a function guarantees its NOT a cancelation point - which we will document with Not Cancelation Point, and typically also noexcept. The DbgTrace () calls fall into this category.

Note
An API marked *** Cancelation Point *** will always CheckForInterruption () at least once (or equivalent check) and will never block indefinitely without periodically checking for interruption.
Stroika threads lifetime must NOT extend outside the lifetime of 'main'. That means they cannot be started by static constructors, and must not be left running past the end of main, to be shut down by static destructors. The reason for this is that its too hard to guarantee all the rest of the Stroika infrastructure (signals etc) work properly in that context, and its do overwhemlingly likely a softare bug waiting to happen.

If you must have a thread running like that, use std::thread (though I'm not sure how well that will work either).

Note the Thread 'smartpointer' wrapper can be constructed/destructed statically (before/after main). You just cannot start a thread before main, nor allow it to still be running (not completed) before exiting. #if qStroika_Foundation_Execution_Thread_SupportThreadStatistics (defaults true in debug builds) an attempt is made to auto-detect this and diagnose it in the tracelog and with assertions.

Note
Considered adding an API to return the underlying std::thread or std::thread::native_handle (), but have not needed this so far, and it might create some confusion about ownership? Detaching would be a problem because the owned thread object has hooks into the Stroika Thread object, so that kind of needs to be left around. I suppose we might want to allow an accessor to std::thread* which is like an internal pointer (thread safety questions too with that). MAYBE better to just allow construction of a Stroika Thread from an std::thread (but then we don't have the wrapper logic for maining reference counts). No compelling need so far, and no obviously best approach.

Typedef Documentation

◆ IDType

Thread::IDType is a portable representation which is a key to currently existing system threads.

Definition at line 216 of file Thread.h.

◆ NativeHandleType

using Stroika::Foundation::Execution::Thread::NativeHandleType = typedef thread::native_handle_type

Thread::native_handle is the type of the underlying handle to a native thread which can allow for using platform APIs.

Definition at line 222 of file Thread.h.

Enumeration Type Documentation

◆ AutoStartFlag

optional flag for constructing new threads

Definition at line 260 of file Thread.h.

◆ Status

Common::DefaultNames<> is defined for this enumeration.

Definition at line 267 of file Thread.h.

◆ Priority

Note
Common::DefaultNames<> supported

Definition at line 280 of file Thread.h.

Function Documentation

◆ CheckForInterruption()

void Stroika::Foundation::Execution::Thread::CheckForInterruption ( )

Our thread interruption (and abort) mechanism only throws at certain 'signalable' (alertable/cancelable) spots in the code - like sleeps, most reads, etc. This function will also trigger a throw if called inside a thread which is being aborted.

Any call to this routine is a 'cancelation point'.

Note
Cancelation Point
This name is somewhat historical, but still reasonable. But now might be better called CheckForThreadAbort, since thats all it does now.

Definition at line 1143 of file Thread.cpp.

◆ IsCurrentThreadInterruptible()

bool Stroika::Foundation::Execution::Thread::IsCurrentThreadInterruptible ( )

Returns true iff it is potentially useful to call CheckForInterruption.

Definition at line 405 of file Thread.inl.

◆ DefaultConfiguration()

Thread::Configuration Stroika::Foundation::Execution::Thread::DefaultConfiguration ( )
noexcept

Return or Set the global default configuration for Thread object construction.

Definition at line 965 of file Thread.cpp.

◆ New()

Thread::Ptr Stroika::Foundation::Execution::Thread::New ( const function< void()> &  fun2CallOnce,
const optional< Characters::String > &  name,
const optional< Configuration > &  configuration 
)

No arg- constructor is available for use in applications like thread pools. Also, a variety of cases, its handy to declare a Thread data member (and init in CTOR), but not specify what gets run until later.

After this call, GetStatus () returns Status::eNull

fun2CallOnce is called precisely once by this thread CTOR, but called in another thread with the arg 'arg'.

Note
about the 'configuration' parameter. If missing (or parts missing) - the whole are parts from from the static GetDefaultConfiguration () function. If defaults are missing there too, the OS / system defaults are relied upon.

It's not expected one would need to use this often, but when you need it you need it early - before the thread has been constructed (generally) - or at least before started (sucks swapping stacks out on a running thread ;-))

Note
Unlike std::thread, a Stroika Thread is not started automatically (unless you pass eAutoStart as a constructor argument), and it can run in the background after the Thread has gone out of scope (std::thread you can do this but must call detach).
Precondition
Debug::AppearsDuringMainLifetime() at all points during the threads lifetime. It must be stopped before Debug::AppearsDuringMainLifetime() becomes untrue. This is somewhat checked by the Stroika thread infrastructure, but may not be fully reliably asserted (see AllThreadsDeadDetector_)
Example Usage
Thread::New ([r]() { r->Run (); }, Thread::eAutoStart); // runs arg to completion then thread destroyed. New returns once thread created
Ptr New(const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
Definition Thread.cpp:955
Example Usage
Thread::Ptr t = Thread::New ([r]() { r->Run (); }, Thread::eAutoStart, "Thread Name");
Thread::Ptr is a (unsynchronized) smart pointer referencing an internally synchronized std::thread ob...
Definition Thread.h:334
Example Usage
Thread::Ptr t = Thread::New ([r]() { r->Run (); });
t.Start ();
nonvirtual void WaitForDone(Time::DurationSeconds timeout=Time::kInfinity) const
Definition Thread.inl:286

Definition at line 955 of file Thread.cpp.

◆ Start()

void Stroika::Foundation::Execution::Thread::Start ( const Traversal::Iterable< Ptr > &  threads)
Precondition
foreach Thread t: t.GetStatus () == Status::eNotYetRunning

Definition at line 347 of file Thread.inl.

◆ Abort()

void Stroika::Foundation::Execution::Thread::Abort ( const Traversal::Iterable< Ptr > &  threads)

foreach Thread t: t.Abort ()

Precondition
foreach Thread t: t != nullptr
See also
Thread::Ptr::Abort

Definition at line 987 of file Thread.cpp.

◆ WaitForDone()

void Stroika::Foundation::Execution::Thread::WaitForDone ( const Traversal::Iterable< Ptr > &  threads,
Time::DurationSeconds  timeout = Time::kInfinity 
)
Note
Cancelation Point
Precondition
foreach Thread t: t != nullptr

Definition at line 351 of file Thread.inl.

◆ WaitForDoneUntil()

void Stroika::Foundation::Execution::Thread::WaitForDoneUntil ( const Traversal::Iterable< Ptr > &  threads,
Time::TimePointSeconds  timeoutAt 
)
Note
Cancelation Point
Precondition
foreach Thread t: t != nullptr

Definition at line 1008 of file Thread.cpp.

◆ AbortAndWaitForDone()

void Stroika::Foundation::Execution::Thread::AbortAndWaitForDone ( const Traversal::Iterable< Ptr > &  threads,
Time::DurationSeconds  timeout = Time::kInfinity 
)

Note
Cancelation Point
See also
Thread::Ptr::AbortAndWaitForDone
Thread::AbortAndWaitForDoneUntil
Note
This frequently (and nearly always in a destructor) - should be preceded by:
Execution::Thread::SuppressInterruptionInContext suppressInterruption; // critical to prohibit this thread from interruption until its killed owned threads
Precondition
foreach Thread t: t != nullptr

Definition at line 343 of file Thread.inl.

◆ AbortAndWaitForDoneUntil()

void Stroika::Foundation::Execution::Thread::AbortAndWaitForDoneUntil ( const Traversal::Iterable< Ptr > &  threads,
Time::TimePointSeconds  timeoutAt 
)
Note
Cancelation Point
See also
Thread::Ptr::AbortAndWaitForDoneUntil
Note
This frequently (and nearly always in a destructor) - should be preceded by:
Execution::Thread::SuppressInterruptionInContext suppressInterruption; // critical to prohibit this thread from interruption until its killed owned threads
Precondition
foreach Thread t: t != nullptr

Definition at line 995 of file Thread.cpp.

◆ FormatThreadID()

wstring Stroika::Foundation::Execution::Thread::FormatThreadID ( Thread::IDType  threadID,
const FormatThreadInfo &  formatInfo = {} 
)

Represent the thread ID for display - typically as an integer.

Note
this function is NOT a cancelation point
this returns an ASCII string (not using String class library) so easier to use from code expecting no cancelation
See also
Characters::ToString (Thread::IDType threadID)

Definition at line 1052 of file Thread.cpp.

◆ GetCurrent()

Thread::Ptr Stroika::Foundation::Execution::Thread::GetCurrent ( )

If the current (calling) thread is a 'Stroika thread' - that thread Ptr is returned. If the current thread is NOT a stroika thread (e.g. the main thread) this will return a nullptr.

Note
SEE http://stroika-bugs.sophists.com/browse/STK-994 - for the idea of creating a special Ptr - just for the main thread. But really no reason I can see –LGP 2023-10-18

Definition at line 371 of file Thread.inl.

◆ Yield()

void Stroika::Foundation::Execution::Thread::Yield ( )

calls CheckForInterruption, and std::this_thread::yield ()

Note
Cancelation Point To avoid cancelation point, directly call std::this_thread::yield ()

Definition at line 1180 of file Thread.cpp.