4#include "Stroika/Foundation/StroikaPreComp.h"
9#include "Stroika/Foundation/Debug/Main.h"
11#include "Stroika/Foundation/Execution/Common.h"
14#include "Stroika/Foundation/Execution/Timeout.h"
24using Memory::MakeSharedPtr;
33class ThreadPool::MyRunnable_ {
36 : fThreadPool{threadPool}
42 bool IsRunning ()
const
44 return fCurTask.fTask !=
nullptr;
46 bool IsRunning (
TaskID task)
const
48 return IsRunning () and fCurTask.fID == task;
52 nonvirtual
void Run ()
60 [[maybe_unused]] lock_guard critSec{fThreadPool.fCriticalSection_};
61 Assert (not IsRunning ());
65 fThreadPool.WaitForNextTask_ (&fCurTask);
67 [[maybe_unused]] lock_guard critSec{fThreadPool.fCriticalSection_};
68 Assert (IsRunning ());
71 [[maybe_unused]]
auto&& cleanup =
Finally ([
this] ()
noexcept {
74 [[maybe_unused]] lock_guard critSec{fThreadPool.fCriticalSection_};
75 fCurTask = PendingTaskInfo_{};
76 taskStartedAt = fCurTaskStartedAt;
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;
89 [[maybe_unused]] lock_guard critSec{fThreadPool.fCriticalSection_};
90 task2Run = fCurTask.fTask;
91 fCurTaskStartedAt = Time::GetTickCount ();
95 catch (
const Thread::AbortException&) {
100 if constexpr (kEmitDbgTraceOnThreadPoolEntryExceptions_ and qStroika_Foundation_Debug_DefaultTracingOn) {
101 DbgTrace (
"in threadpool, ignoring exception running task: {}"_f, current_exception ());
110 PendingTaskInfo_ fCurTask;
123 sb <<
"numberOfTasksAdded: "sv << fNumberOfTasksAdded;
125 sb <<
", totalTimeConsumed: "sv << fTotalTimeConsumed;
136 : fCollectStatistics_{options.fCollectStatistics}
137 , fDefaultQMax_{options.fQMax}
138 , fThreadPoolName_{options.fThreadPoolName}
140 Require (Debug::AppearsDuringMainLifetime ());
146 Require (Debug::AppearsDuringMainLifetime ());
147 AbortAndWaitForDone_ ();
152 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
153 return static_cast<unsigned int> (fThreads_.
size ());
159 Require (not fAborted_);
160 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
162 while (poolSize > fThreads_.
size ()) {
163 fThreads_.
Add (mkThread_ ());
167 while (poolSize < fThreads_.
size ()) {
169 bool anyFoundToKill =
false;
171 if (not i->fRunnable->IsRunning ()) {
174 anyFoundToKill =
true;
178 if (not anyFoundToKill) {
180 DbgTrace (
"Failed to lower the loop size - cuz all threads busy - giving up"_f);
191#if qStroika_Foundation_Debug_AssertionsChecked
192 bool blockedAtLeastOnce =
false;
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;
206 return AddTask_ (task, name);
211auto ThreadPool::AddTask_ (
const TaskType& task,
const optional<Characters::String>& name) ->
TaskID
213#if USE_NOISY_TRACE_IN_THIS_MODULE_
216 Require (not fAborted_);
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 ());
224 ++fCollectedTaskStats_.fNumberOfTasksAdded;
228 fTasksMaybeAdded_.
Set ();
230 Require (not fAborted_);
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);
261 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
263 if (i->fRunnable->IsRunning (task)) {
264 thread2Kill = i->fThread;
265 fThreads_.
Update (i, mkThread_ ());
270 if (thread2Kill !=
nullptr) {
280 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
281 fPendingTasks_.clear ();
282 for (
const TPInfo_& ti : fThreads_) {
288 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
289 for (
const TPInfo_& ti : fThreads_) {
291 ti.fThread.AbortAndWaitForDone (timeout);
303 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
304 for (
auto i = fPendingTasks_.begin (); i != fPendingTasks_.end (); ++i) {
305 if (i->fID == task) {
310 for (
auto i = fThreads_.
begin (); i != fThreads_.
end (); ++i) {
311 if (i->fRunnable->IsRunning (task)) {
321 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
322 for (
auto i = fThreads_.
begin (); i != fThreads_.
end (); ++i) {
323 if (i->fRunnable->IsRunning (task)) {
335 TimePointSeconds timeoutAt = timeout + Time::GetTickCount ();
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});
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});
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);
377 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
378 size_t count = fPendingTasks_.size ();
379 for (
auto i = fThreads_.
begin (); i != fThreads_.
end (); ++i) {
381 if (i->fRunnable->IsRunning ()) {
391 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
392 for (
const auto& i : fPendingTasks_) {
400 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
401 return fPendingTasks_.size ();
406#if USE_NOISY_TRACE_IN_THIS_MODULE_
408 "ThreadPool::WaitForTasksDoneUntil",
"*this={}, tasks={}, timeoutAt={}"_f,
ToString (),
ToString (tasks), timeoutAt)};
411 for (
const auto& task : tasks) {
412 auto now = Time::GetTickCount ();
420#if USE_NOISY_TRACE_IN_THIS_MODULE_
422 "*this={}, timeoutAt={}"_f,
ToString (), timeoutAt)};
434 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
435 Require (fCollectStatistics_);
436 fCollectedTaskStats_ = {};
441 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
442 Require (fCollectStatistics_);
443 return fCollectedTaskStats_;
446void ThreadPool::Abort_ () noexcept
455 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
456 fPendingTasks_.clear ();
457 for (
const TPInfo_& ti : fThreads_) {
463void ThreadPool::AbortAndWaitForDone_ () noexcept
466#if USE_NOISY_TRACE_IN_THIS_MODULE_
474 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
475 fThreads_.
Apply ([&] (
const TPInfo_& i) { threadsToShutdown.
Add (i.fThread); });
479 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
480 fThreads_.
Apply ([&] (
const TPInfo_& i) { Assert (i.fThread.IsDone ()); });
485 DbgTrace (
"ThreadPool::AbortAndWaitForDone_: serious bug/exception"_f);
488 Ensure (fThreads_.
empty ());
496 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
497 if (fThreadPoolName_) {
498 sb <<
"pool-name: '{}'"_f(*fThreadPoolName_);
501 if (fThreadPoolName_) {
507 [[maybe_unused]] lock_guard critSec{fCriticalSection_};
508 sb <<
", pool-thread-count: {}"_f(fThreads_.
size ());
515void ThreadPool::WaitForNextTask_ (PendingTaskInfo_* result)
518 Require (result->fTask ==
nullptr);
520 if (fAborted_) [[unlikely]] {
521 Throw (Thread::AbortException::kThe);
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);
543ThreadPool::TPInfo_ ThreadPool::mkThread_ ()
545 shared_ptr<MyRunnable_> r{MakeSharedPtr<ThreadPool::MyRunnable_> (*
this)};
546 StringBuilder entryName =
"TPE #{}"_f(fNextThreadEntryNumber_++);
547 entryName +=
" {"sv + fThreadPoolName_.value_or (
"anonymous-thread-pool"sv) +
"}"sv;
549 return TPInfo_{t, r};
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
#define RequireNotNull(p)
#define AssertNotReached()
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
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,...
static UniqueID New()
One never given before in this process - by New () of any TAG.
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...
nonvirtual void AbortAndWaitForDone(Time::DurationSeconds timeout=Time::kInfinity) const
Abort () the thread, and then WaitForDone () (aka AbortAndWaitForDoneUntil (timeout + GetTickCount))
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 ...
nonvirtual size_t GetTasksCount() const
return total number of tasks, either pending, or currently running.
nonvirtual Characters::String ToString() const
function< void()> TaskType
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 ResetStatistics()
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.
nonvirtual size_t size() const
Returns the number of items contained.
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.
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.
Ptr New(const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
void CheckForInterruption()
void AbortAndWaitForDone(const Traversal::Iterable< Ptr > &threads, Time::DurationSeconds timeout=Time::kInfinity)
shorthand for AbortAndWaitForDoneUntil (Time::GetTickCount () + timeout)
void Sleep(Time::Duration seconds2Wait)
void ThrowTimeoutExceptionAfter(Time::TimePointSeconds afterTickCount, EXCEPTION &&exception2Throw)
How Stroika reports a timeout.
void Throw(T &&e2Throw)
like the builtin C++ 'throw', but stamps in the current Activity context and does helpful,...
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
unsigned int fNumberOfTasksCompleted
nonvirtual Characters::String ToString() const