Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
ThreadPool.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
9#include "Stroika/Foundation/Debug/Main.h"
11#include "Stroika/Foundation/Execution/Common.h"
14#include "Stroika/Foundation/Execution/Timeout.h"
16
17#include "ThreadPool.h"
18
19using namespace Stroika::Foundation;
22using namespace Stroika::Foundation::Execution;
23
24using Memory::MakeSharedPtr;
25
26// Comment this in to turn on aggressive noisy DbgTrace in this module
27//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
28
29namespace {
30 constexpr bool kEmitDbgTraceOnThreadPoolEntryExceptions_ = qStroika_Foundation_Debug_AssertionsChecked;
31}
32
33class ThreadPool::MyRunnable_ {
34public:
35 MyRunnable_ (ThreadPool& threadPool)
36 : fThreadPool{threadPool}
37 {
38 }
39
40public:
41 // whether this thread is running a task - and if so, the task (assumes the caller holds fThreadPool.fCriticalSection_)
42 bool IsRunning () const
43 {
44 return fCurTask.fTask != nullptr;
45 }
46 bool IsRunning (TaskID task) const
47 {
48 return IsRunning () and fCurTask.fID == task;
49 }
50
51public:
52 nonvirtual void Run ()
53 {
54 // For NOW - allow ThreadAbort to just kill this thread. In the future - we may want to implement some sort of restartability
55
56 // Keep grabbing new tasks, and running them
57 while (true) {
58 {
60 [[maybe_unused]] lock_guard critSec{fThreadPool.fCriticalSection_};
61 Assert (not IsRunning ());
62 }
63 // Subtle point, but we must copy directly into fCurTask (WaitForNextTask_ call) so its filled in under lock
64 // while being moved so task moves from pending to in-use without ever temporarily disappearing from known tasks lists
65 fThreadPool.WaitForNextTask_ (&fCurTask); // This will block INDEFINITELY until ThreadAbort throws out or we have a new task to run
67 [[maybe_unused]] lock_guard critSec{fThreadPool.fCriticalSection_};
68 Assert (IsRunning ());
69 }
70 }
71 [[maybe_unused]] auto&& cleanup = Finally ([this] () noexcept {
72 Time::TimePointSeconds taskStartedAt;
73 {
74 [[maybe_unused]] lock_guard critSec{fThreadPool.fCriticalSection_};
75 fCurTask = PendingTaskInfo_{};
76 taskStartedAt = fCurTaskStartedAt;
77 }
78 if (fThreadPool.fCollectStatistics_) {
79 [[maybe_unused]] lock_guard critSec{fThreadPool.fCriticalSection_};
80 ++fThreadPool.fCollectedTaskStats_.fNumberOfTasksCompleted;
81 ++fThreadPool.fCollectedTaskStats_.fNumberOfTasksReporting;
82 fThreadPool.fCollectedTaskStats_.fTotalTimeConsumed += Time::GetTickCount () - taskStartedAt;
83 }
84 });
85 try {
86 // Use lock to access fCurTask, but don't hold the lock during run, so others can call getcurrenttask
87 ThreadPool::TaskType task2Run;
88 {
89 [[maybe_unused]] lock_guard critSec{fThreadPool.fCriticalSection_};
90 task2Run = fCurTask.fTask;
91 fCurTaskStartedAt = Time::GetTickCount ();
92 }
93 task2Run ();
94 }
95 catch (const Thread::AbortException&) {
96 throw; // cancel this thread
97 }
98 catch (...) {
99 // other exceptions WARNING WITH DEBUG MESSAGE - but otherwise - EAT/IGNORE
100 if constexpr (kEmitDbgTraceOnThreadPoolEntryExceptions_ and qStroika_Foundation_Debug_DefaultTracingOn) {
101 DbgTrace ("in threadpool, ignoring exception running task: {}"_f, current_exception ());
102 }
103 }
104 }
105 }
106
107public:
108 ThreadPool& fThreadPool;
109 // fThreadPool.fCriticalSection_ protect access to fCurTask/fCurTaskStartedAt - very short duration
110 PendingTaskInfo_ fCurTask; // the task running, its ID and name; fTask null if none
111 Time::TimePointSeconds fCurTaskStartedAt{0s}; // meaningless if not IsRunning ()
112};
113
114/*
115 ********************************************************************************
116 ************************ Execution::ThreadPool::Statistics *********************
117 ********************************************************************************
118 */
120{
121 StringBuilder sb;
122 sb << "{"sv;
123 sb << "numberOfTasksAdded: "sv << fNumberOfTasksAdded;
124 sb << ", numberOfTasksCompleted: "sv << fNumberOfTasksCompleted;
125 sb << ", totalTimeConsumed: "sv << fTotalTimeConsumed;
126 sb << "}"sv;
127 return sb;
128}
129
130/*
131 ********************************************************************************
132 ****************************** Execution::ThreadPool ***************************
133 ********************************************************************************
134 */
135ThreadPool::ThreadPool (const Options& options)
136 : fCollectStatistics_{options.fCollectStatistics}
137 , fDefaultQMax_{options.fQMax}
138 , fThreadPoolName_{options.fThreadPoolName}
139{
140 Require (Debug::AppearsDuringMainLifetime ());
141 SetPoolSize (options.fThreadCount);
142}
143
145{
146 Require (Debug::AppearsDuringMainLifetime ());
147 AbortAndWaitForDone_ ();
148}
149
150unsigned int ThreadPool::GetPoolSize () const
151{
152 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
153 return static_cast<unsigned int> (fThreads_.size ());
154}
155
156void ThreadPool::SetPoolSize (unsigned int poolSize)
157{
158 Debug::TraceContextBumper ctx{"ThreadPool::SetPoolSize", "newPoolSize={}, *this={}"_f, poolSize, ToString ()};
159 Require (not fAborted_);
160 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
161 DbgTrace ("fThreads_.size ()={}"_f, fThreads_.size ());
162 while (poolSize > fThreads_.size ()) {
163 fThreads_.Add (mkThread_ ());
164 }
165
166 // Still quite weak implementation of REMOVAL
167 while (poolSize < fThreads_.size ()) {
168 // iterate over threads if any not busy, remove that them first
169 bool anyFoundToKill = false;
170 for (Iterator<TPInfo_> i = fThreads_.begin (); i != fThreads_.end (); ++i) {
171 if (not i->fRunnable->IsRunning ()) {
172 // since we have fCriticalSection_ - we can safely remove this thread
173 fThreads_.Remove (i);
174 anyFoundToKill = true;
175 break;
176 }
177 }
178 if (not anyFoundToKill) {
179 // @todo - fix this better/eventually - either throw or wait...
180 DbgTrace ("Failed to lower the loop size - cuz all threads busy - giving up"_f);
181 return;
182 }
183 }
184}
185
186auto ThreadPool::AddTask (const TaskType& task, QMax qmax, const optional<Characters::String>& name) -> TaskID
187{
188 // also INTENTIONALLY dont hold lock long enuf to make this work 100% reliably cuz these magic numbers dont need to be precise, just approximate
189 // @todo rewrite this with condition variables, so more efficient and wakes up/times out appropriately...
190 Time::TimePointSeconds timeoutAt = Time::GetTickCount () + qmax.fAddBlockTimeout;
191#if qStroika_Foundation_Debug_AssertionsChecked
192 bool blockedAtLeastOnce = false;
193#endif
194 while (true) {
195 if (GetPendingTasksCount () >= qmax.fLength) [[unlikely]] {
196#if qStroika_Foundation_Debug_AssertionsChecked
197 if (not blockedAtLeastOnce) {
198 DbgTrace ("Blocking inside ThreadPool::AddTask due to excessive pending task count"_f);
199 blockedAtLeastOnce = true;
200 }
201#endif
202 ThrowTimeoutExceptionAfter (timeoutAt);
203 Execution::Sleep (500ms); // @todo fix and use condition variable - but good news is can only happen if fAddBlockTimeout != 0s
204 }
205 else {
206 return AddTask_ (task, name);
207 }
208 }
209}
210
211auto ThreadPool::AddTask_ (const TaskType& task, const optional<Characters::String>& name) -> TaskID
212{
213#if USE_NOISY_TRACE_IN_THIS_MODULE_
214 Debug::TraceContextBumper ctx{"ThreadPool::AddTask_"};
215#endif
216 Require (not fAborted_);
217 const TaskID id = TaskID::New ();
218 {
219 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
220 fPendingTasks_.push_back (PendingTaskInfo_{.fID = id, .fTask = task, .fName = name});
221#if USE_NOISY_TRACE_IN_THIS_MODULE_
222 DbgTrace ("fPendingTasks.size () now = {}"_f, (int)fPendingTasks_.size ());
223#endif
224 ++fCollectedTaskStats_.fNumberOfTasksAdded;
225 }
226
227 // Notify any waiting threads to wakeup and claim the next task
228 fTasksMaybeAdded_.Set ();
229 // this would be a race - if aborting and adding tasks at the same time
230 Require (not fAborted_);
231 return id;
232}
233
235{
236 Debug::TraceContextBumper ctx{"ThreadPool::AbortTask"};
237 {
238 // First see if its in the Q
239 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
240 for (auto i = fPendingTasks_.begin (); i != fPendingTasks_.end (); ++i) {
241 if (i->fID == task) {
242 fPendingTasks_.erase (i);
243 return;
244 }
245 }
246 }
247
248 // If we got here - its NOT in the task Q, so maybe its already running.
249 //
250 //
251
252 // TODO:
253 // We walk the list of existing threads and ask each one if its (indirected - running task) is the given one and abort that task.
254 // But that requires we can RESTART an ABORTED thread (or that we remove it from the list - maybe thats better). THat COULD be OK
255 // actually since it involves on API changes and makes sense. The only slight issue is a peformace one but probably for something
256 // quite rare.
257 //
258 // Anyhow SB OK for now to just not allow aborting a task which has already started....
259 Thread::Ptr thread2Kill;
260 {
261 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
262 for (Iterator<TPInfo_> i = fThreads_.begin (); i != fThreads_.end (); ++i) {
263 if (i->fRunnable->IsRunning (task)) {
264 thread2Kill = i->fThread;
265 fThreads_.Update (i, mkThread_ ());
266 break;
267 }
268 }
269 }
270 if (thread2Kill != nullptr) {
271 thread2Kill.AbortAndWaitForDone (timeout);
272 }
273}
274
276{
277 Debug::TraceContextBumper ctx{"ThreadPool::AbortTasks"};
278 auto tps = GetPoolSize ();
279 {
280 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
281 fPendingTasks_.clear ();
282 for (const TPInfo_& ti : fThreads_) {
283 ti.fThread.Abort ();
284 }
285 }
286 {
287 // @todo maybe fix unsafe - waiting here holding the critsec lock - seems deadlock waiting to happen - LGP 2023-11-05
288 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
289 for (const TPInfo_& ti : fThreads_) {
290 // @todo fix wrong timeout value here
291 ti.fThread.AbortAndWaitForDone (timeout);
292 }
293 fThreads_.RemoveAll ();
294 }
295 // hack - not a good design or impl!!! - waste to recreate if not needed!
296 SetPoolSize (tps);
297}
298
300{
301 {
302 // First see if its in the Q
303 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
304 for (auto i = fPendingTasks_.begin (); i != fPendingTasks_.end (); ++i) {
305 if (i->fID == task) {
306 return true;
307 }
308 }
309 // then check if running
310 for (auto i = fThreads_.begin (); i != fThreads_.end (); ++i) {
311 if (i->fRunnable->IsRunning (task)) {
312 return true;
313 }
314 }
315 }
316 return false;
317}
318
320{
321 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
322 for (auto i = fThreads_.begin (); i != fThreads_.end (); ++i) {
323 if (i->fRunnable->IsRunning (task)) {
324 return true;
325 }
326 }
327 return false;
328}
329
331{
332 Debug::TraceContextBumper ctx{"ThreadPool::WaitForTask"};
333 // Inefficient / VERY SLOPPY impl - @todo fix use WaitableEvent or condition variables...
335 TimePointSeconds timeoutAt = timeout + Time::GetTickCount ();
336 while (true) {
337 if (not IsPresent (task)) {
338 return;
339 }
340 Time::DurationSeconds remaining = timeoutAt - Time::GetTickCount ();
341 ThrowTimeoutExceptionAfter (timeoutAt);
342 Execution::Sleep (min<Time::DurationSeconds> (remaining, 1.0s));
343 }
344}
345
346auto ThreadPool::GetTasks () const -> Collection<TaskInfo>
347{
349 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
350 for (const auto& ti : fPendingTasks_) {
351 result.Add (TaskInfo{.fID = ti.fID, .fTask = ti.fTask, .fName = ti.fName});
352 }
353 for (auto i = fThreads_.begin (); i != fThreads_.end (); ++i) {
354 if (i->fRunnable->IsRunning ()) {
355 const PendingTaskInfo_& cur = i->fRunnable->fCurTask;
356 result.Add (TaskInfo{.fID = cur.fID, .fTask = cur.fTask, .fName = cur.fName, .fRunningSince = i->fRunnable->fCurTaskStartedAt});
357 }
358 }
359 return result;
360}
361
363{
364 Collection<TaskID> result;
365 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
366 for (auto i = fThreads_.begin (); i != fThreads_.end (); ++i) {
367 if (i->fRunnable->IsRunning ()) {
368 result.Add (i->fRunnable->fCurTask.fID);
369 }
370 }
371 return result;
372}
373
375{
376 // First see if its in the Q
377 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
378 size_t count = fPendingTasks_.size ();
379 for (auto i = fThreads_.begin (); i != fThreads_.end (); ++i) {
380 AssertNotNull (i->fRunnable);
381 if (i->fRunnable->IsRunning ()) {
382 ++count;
383 }
384 }
385 return count;
386}
387
389{
390 Collection<TaskID> result;
391 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
392 for (const auto& i : fPendingTasks_) {
393 result.Add (i.fID);
394 }
395 return result;
396}
397
399{
400 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
401 return fPendingTasks_.size ();
402}
403
405{
406#if USE_NOISY_TRACE_IN_THIS_MODULE_
408 "ThreadPool::WaitForTasksDoneUntil", "*this={}, tasks={}, timeoutAt={}"_f, ToString (), ToString (tasks), timeoutAt)};
409#endif
411 for (const auto& task : tasks) {
412 auto now = Time::GetTickCount ();
413 ThrowTimeoutExceptionAfter (timeoutAt);
414 WaitForTask (task, timeoutAt - now);
415 }
416}
417
419{
420#if USE_NOISY_TRACE_IN_THIS_MODULE_
421 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("ThreadPool::WaitForTasksDoneUntil",
422 "*this={}, timeoutAt={}"_f, ToString (), timeoutAt)};
423#endif
425 // @todo - use WaitablEvent - this is a horrible implementation
426 while (GetTasksCount () != 0) {
427 ThrowTimeoutExceptionAfter (timeoutAt);
428 Execution::Sleep (100ms);
429 }
430}
431
433{
434 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
435 Require (fCollectStatistics_);
436 fCollectedTaskStats_ = {};
437}
438
440{
441 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
442 Require (fCollectStatistics_);
443 return fCollectedTaskStats_;
444}
445
446void ThreadPool::Abort_ () noexcept
447{
448 Thread::SuppressInterruptionInContext suppressCtx; // must cleanly shut down each of our sub-threads - even if our thread is aborting... don't be half-way aborted
449 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("ThreadPool::Abort_", "*this={}"_f, ToString ())};
450 Debug::TimingTrace tt{1.0s};
451 fAborted_ = true; // No race, because fAborted never 'unset'
452 // no need to set fTasksMaybeAdded_, since aborting each thread should be sufficient
453 {
454 // Clear the task Q and then abort each thread
455 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
456 fPendingTasks_.clear ();
457 for (const TPInfo_& ti : fThreads_) {
458 ti.fThread.Abort ();
459 }
460 }
461}
462
463void ThreadPool::AbortAndWaitForDone_ () noexcept
464{
465 Thread::SuppressInterruptionInContext suppressCtx; // cuz we must shutdown owned threads
466#if USE_NOISY_TRACE_IN_THIS_MODULE_
467 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("ThreadPool::AbortAndWaitForDone_", "*this={}"_f, ToString ())};
468 Debug::TimingTrace tt{1.0s};
469#endif
470 try {
471 Abort_ (); // to get the rest of the threadpool abort stuff triggered - flag saying aborting
472 Collection<Thread::Ptr> threadsToShutdown;
473 {
474 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
475 fThreads_.Apply ([&] (const TPInfo_& i) { threadsToShutdown.Add (i.fThread); });
476 }
477 Thread::AbortAndWaitForDone (threadsToShutdown);
478 {
479 [[maybe_unused]] lock_guard critSec{fCriticalSection_}; // they should all be shutdown now
480 fThreads_.Apply ([&] (const TPInfo_& i) { Assert (i.fThread.IsDone ()); });
481 fThreads_.RemoveAll ();
482 }
483 }
484 catch (...) {
485 DbgTrace ("ThreadPool::AbortAndWaitForDone_: serious bug/exception"_f);
486 AssertNotReached (); // this should never happen due to the SuppressInterruptionInContext...
487 }
488 Ensure (fThreads_.empty ());
489}
490
492{
493 StringBuilder sb;
494 sb << "{"sv;
495 {
496 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
497 if (fThreadPoolName_) {
498 sb << "pool-name: '{}'"_f(*fThreadPoolName_);
499 }
500 }
501 if (fThreadPoolName_) {
502 sb << ", "sv;
503 }
504 sb << "pending-task-count: {}"_f(GetPendingTasksCount ());
505 sb << ", running-task-count: {}"_f(GetRunningTasks ().size ());
506 {
507 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
508 sb << ", pool-thread-count: {}"_f(fThreads_.size ());
509 }
510 sb << "}"sv;
511 return sb;
512}
513
514// THIS is called NOT from 'this' - but from the context of an OWNED thread of the pool
515void ThreadPool::WaitForNextTask_ (PendingTaskInfo_* result)
516{
517 RequireNotNull (result);
518 Require (result->fTask == nullptr);
519 while (true) {
520 if (fAborted_) [[unlikely]] {
521 Throw (Thread::AbortException::kThe);
522 }
523
524 {
525 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
526 if (not fPendingTasks_.empty ()) {
527 *result = fPendingTasks_.front ();
528 fPendingTasks_.pop_front ();
529 DbgTrace ("ThreadPool::WaitForNextTask_ () pulled a new task from 'pending-tasks' to run on this thread, leaving pending-task-list-size = {}"_f,
530 fPendingTasks_.size ());
531 Ensure (result->fTask != nullptr);
532 return;
533 }
534 }
535
536 // Prevent spinwaiting... This event is SET when any new item arrives
537 //DbgTrace ("ThreadPool::WaitForNextTask_ () - about to wait for added tasks");
538 fTasksMaybeAdded_.WaitAndReset ();
539 //DbgTrace ("ThreadPool::WaitForNextTask_ () - completed wait for added tasks");
540 }
541}
542
543ThreadPool::TPInfo_ ThreadPool::mkThread_ ()
544{
545 shared_ptr<MyRunnable_> r{MakeSharedPtr<ThreadPool::MyRunnable_> (*this)};
546 StringBuilder entryName = "TPE #{}"_f(fNextThreadEntryNumber_++); // make name so short cuz unix only shows first 15 chars - http://man7.org/linux/man-pages/man3/pthread_setname_np.3.html
547 entryName += " {"sv + fThreadPoolName_.value_or ("anonymous-thread-pool"sv) + "}"sv;
548 Thread::Ptr t = Thread::New ([r] () { r->Run (); }, Thread::eAutoStart, entryName); // race condition for updating this number, but who cares - its purely cosmetic...
549 return TPInfo_{t, r};
550}
#define AssertNotNull(p)
Definition Assertions.h:336
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
Definition Assertions.h:49
#define RequireNotNull(p)
Definition Assertions.h:350
#define AssertNotReached()
Definition Assertions.h:358
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:327
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:288
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
static UniqueID New()
One never given before in this process - by New () of any TAG.
Definition UniqueID.inl:19
A Collection<T> is a container to manage an un-ordered collection of items, without equality defined ...
nonvirtual void Update(const Iterator< value_type > &i, ArgByValueType< value_type > newValue, Iterator< value_type > *nextI=nullptr)
nonvirtual void Remove(ArgByValueType< value_type > item, EQUALS_COMPARER &&equalsComparer={})
Remove () the argument value (which must exist)
nonvirtual void Add(ArgByValueType< value_type > item)
nonvirtual void RemoveAll()
RemoveAll removes all, or all matching (predicate, iterator range, equals comparer or whatever) items...
Thread::Ptr is a (unsynchronized) smart pointer referencing an internally synchronized std::thread ob...
Definition Thread.h:357
nonvirtual void AbortAndWaitForDone(Time::DurationSeconds timeout=Time::kInfinity) const
Abort () the thread, and then WaitForDone () (aka AbortAndWaitForDoneUntil (timeout + GetTickCount))
Definition Thread.inl:291
nonvirtual void AbortTask(TaskID task, Time::DurationSeconds timeout=Time::kInfinity)
nonvirtual size_t GetPendingTasksCount() const
return GetPendingTasks ().size (), except probably much more efficient
nonvirtual Containers::Collection< TaskID > GetRunningTasks() const
nonvirtual void AbortTasks(Time::DurationSeconds timeout=Time::kInfinity)
nonvirtual Containers::Collection< TaskInfo > GetTasks() const
returns GetPendingTasks () + GetRunningTasks () - but also some extra information about each task
nonvirtual bool IsRunning(TaskID task) const
nonvirtual Statistics GetCurrentStatistics() const
nonvirtual TaskID AddTask(const TaskType &task, const optional< Characters::String > &name=nullopt)
nonvirtual bool IsPresent(TaskID task) const
Common::UniqueID< ThreadPool > TaskID
Names a task AddTask added - for AbortTask, IsPresent, IsRunning and WaitForTask. No two the same in ...
Definition ThreadPool.h:168
nonvirtual size_t GetTasksCount() const
return total number of tasks, either pending, or currently running.
nonvirtual Characters::String ToString() const
nonvirtual void WaitForTasksDoneUntil(const Traversal::Iterable< TaskID > &tasks, Time::TimePointSeconds timeoutAt) const
nonvirtual void WaitForTask(TaskID task, Time::DurationSeconds timeout=Time::kInfinity) const
nonvirtual void SetPoolSize(unsigned int poolSize)
nonvirtual Containers::Collection< TaskID > GetPendingTasks() const
nonvirtual unsigned int GetPoolSize() const
nonvirtual void WaitAndReset(Time::Duration timeout=Time::kInfinity)
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:234
nonvirtual size_t size() const
Returns the number of items contained.
Definition Iterable.inl:311
nonvirtual void Apply(const function< void(ArgByValueType< T > item)> &doToElement) const
Run the argument function (or lambda) on each element of the container.
nonvirtual Iterator< T > begin() const
Support for ranged for, and STL syntax in general.
nonvirtual bool empty() const
Returns true iff size() == 0.
Definition Iterable.inl:317
static constexpr default_sentinel_t end() noexcept
Support for ranged for, and STL syntax in general.
An Iterator<T> is a copyable object which allows traversing the contents of some container.
Definition Iterator.h:253
Ptr New(const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
Definition Thread.cpp:887
void AbortAndWaitForDone(const Traversal::Iterable< Ptr > &threads, Time::DurationSeconds timeout=Time::kInfinity)
shorthand for AbortAndWaitForDoneUntil (Time::GetTickCount () + timeout)
Definition Thread.inl:343
void Sleep(Time::Duration seconds2Wait)
Definition Sleep.inl:101
void ThrowTimeoutExceptionAfter(Time::TimePointSeconds afterTickCount, EXCEPTION &&exception2Throw)
How Stroika reports a timeout.
Definition Timeout.inl:21
void Throw(T &&e2Throw)
like the builtin C++ 'throw', but stamps in the current Activity context and does helpful,...
Definition Throw.inl:54
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
Definition Finally.inl:31
nonvirtual Characters::String ToString() const