From 8b3f9e17cda678c265d31390f6d49d55dac2dcc9 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 10 Oct 2023 19:47:39 +0200 Subject: [PATCH] Library: scaffolding to install thread lifecycle hooks to cover the identified use-cases a wide variety of functors must be accepted and adapted appropriately. A special twist arises from the fact that the complete thread-wrapper component stack works without RTTI; a derived class can not access the thread-wrapper internals while the policy component to handle those hooks can not directly downcast to some derived user provided class. But obviously at usage site it can be expected to access both realms from such a callback. The solution is to detect the argument type of the given functor and to build a two step path for a safe static cast. --- src/lib/meta/function.hpp | 1 + src/lib/thread.hpp | 71 +++++++++++++----- .../library/thread-wrapper-lifecycle-test.cpp | 73 ++++++++----------- wiki/thinkPad.ichthyo.mm | 47 +++++++++++- 4 files changed, 130 insertions(+), 62 deletions(-) diff --git a/src/lib/meta/function.hpp b/src/lib/meta/function.hpp index 4f5617601..13ae3d097 100644 --- a/src/lib/meta/function.hpp +++ b/src/lib/meta/function.hpp @@ -128,6 +128,7 @@ namespace meta{ using Args = Types; using Sig = RET(ARGS...); using Functor = std::function; + enum { ARITY = sizeof...(ARGS) }; }; /** Specialisation to strip `noexcept` from the signature */ diff --git a/src/lib/thread.hpp b/src/lib/thread.hpp index 70e4ee91f..d66b1a2d6 100644 --- a/src/lib/thread.hpp +++ b/src/lib/thread.hpp @@ -124,6 +124,7 @@ #include "lib/nocopy.hpp" #include "include/logging.h" #include "lib/meta/trait.hpp" +#include "lib/meta/function.hpp" #include "lib/format-util.hpp" #include "lib/result.hpp" @@ -146,6 +147,7 @@ namespace lib { namespace thread {// Thread-wrapper base implementation... using lib::meta::typeSymbol; + using lib::meta::_Fun; using std::function; using std::forward; using std::move; @@ -302,24 +304,18 @@ namespace lib { using BasePol = PolicyLaunchOnly; using BasePol::BasePol; - using Hook = function; + using Self = PolicyLifecycleHook; + using Hook = function; Hook hook_beginThread{}; Hook hook_afterThread{}; Hook hook_looseThread{}; - TAR& - castInstance() - { - return static_cast( - static_cast (this)); - } - void handle_begin_thread() { if (hook_beginThread) - hook_beginThread (castInstance()); + hook_beginThread (*this); else BasePol::handle_begin_thread(); } @@ -328,16 +324,16 @@ namespace lib { handle_after_thread() { if (hook_afterThread) - hook_afterThread (castInstance()); - else - BasePol::handle_after_thread(); + hook_afterThread (*this); + if (BAS::isLive()) // Note: ensure thread is detached at end + BAS::threadImpl_.detach(); } void handle_loose_thread() { if (hook_looseThread) - hook_looseThread (castInstance()); + hook_looseThread (*this); else BasePol::handle_loose_thread(); } @@ -486,7 +482,7 @@ namespace lib { template Launch&& - atEnd (HOOK&& hook) + atExit (HOOK&& hook) { return addHook (&Policy::hook_afterThread, forward (hook)); } @@ -503,11 +499,29 @@ namespace lib { Launch&& addHook (FUN Policy::*storedHook, HOOK&& hook) { - return addLayer ([storedHook, hook = forward(hook)] + static_assert(1 == _Fun::ARITY); + static_assert(1 >= _Fun::ARITY); + using Arg = typename _Fun::Args::List::Head; + FUN adapted; + if constexpr (0 == _Fun::ARITY) + { + adapted = [hook = forward(hook)](Arg){ hook(); }; + } + else + { + using Target = typename _Fun::Args::List::Head; + adapted = [hook = forward(hook)] + (Arg& threadWrapper) + { + ThreadLifecycle& base = static_cast (threadWrapper); + Target& target = static_cast (base); + hook (target); + }; + } + return addLayer ([storedHook, hook = move(adapted)] (ThreadLifecycle& wrapper) { - wrapper.*storedHook = move (hook); - chain (wrapper); + wrapper.*storedHook = move(hook); }); } @@ -665,6 +679,29 @@ namespace lib { + /************************************************************************//** + * Extended variant of the [standard case](\ref Thread), allowing to install + * callbacks (hook functions) to be invoked during thread lifecycle: + * - `atStart` : invoked as first user code in the new thread + * - `atExit` : invoked as the last user code prior to detaching and thread end + * - `onOrphan` : invoked from the thread-wrapper destructor, when the actual + * thread is detected as still running (according to the thread handle) + * By default, these callbacks are empty; custom callbacks can be installed + * through the ThreadLifecycle::Launch configuration builder using the + * corresponding builder functions (e.g. `.atExit(λ)`). The passed functor + * can either take no argument, or a single argument with a reference to + * some `*this` subtype, which must be reachable by static downcast from + * the ThreadLifecycle base type. + */ + class ThreadHookable + : public thread::ThreadLifecycle + { + public: + using ThreadLifecycle::ThreadLifecycle; + }; + + + /************************************************************************//** * Special configuration for a »fire-and-forget«-Thread. * @internal this class is meant for subclassing. Start with #launchDetached() diff --git a/tests/library/thread-wrapper-lifecycle-test.cpp b/tests/library/thread-wrapper-lifecycle-test.cpp index 6e16b5be5..b6be52b3e 100644 --- a/tests/library/thread-wrapper-lifecycle-test.cpp +++ b/tests/library/thread-wrapper-lifecycle-test.cpp @@ -40,7 +40,7 @@ using lib::explore; using std::atomic_uint; using std::this_thread::yield; using std::this_thread::sleep_for; -using std::chrono::microseconds; +using namespace std::chrono_literals; using std::chrono::system_clock; @@ -69,7 +69,8 @@ namespace test{ run (Arg) { defaultWrapperLifecycle(); - verifyExplicitLifecycleState(); + verifyThreadLifecycleHooks(); + demonstrateExplicitThreadLifecycle(); } @@ -90,14 +91,6 @@ namespace test{ double offset = Dur{threadStart - afterCtor}.count(); SHOW_EXPR(offset) CHECK (offset > 0); - - Thread murks{Thread::Launch([&](uint scope) - { - cout << "Hello nested world "< threads{NUM_THREADS}; - - size_t checkSum = 0; - size_t globalSum = 0; - auto launchThreads = [&] - { - for (uint i=1; i<=NUM_THREADS; ++i) - { - uint x = rand() % 1000; - globalSum += (i + x); - threads.emplace (&TestThread::doIt, i, x); - } // Note: bind to member function, copying arguments - - while (explore(threads).has_any()) - yield(); // wait for all threads to have detached - - for (auto& t : threads) - { - CHECK (0 < t.local); - checkSum += t.local; - } - }; - - double runTime = benchmarkTime (launchThreads, REPETITIONS); - - CHECK (checkSum == globalSum); // sum of precomputed random numbers matches sum from threads - CHECK (runTime < NUM_THREADS * 1000/2); // random sleep time should be > 500ms on average } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 056657e52..6d5ed6361 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -65242,7 +65242,7 @@ - + @@ -65262,6 +65262,10 @@ + + + + @@ -65386,7 +65390,8 @@ - + + @@ -65395,7 +65400,7 @@ - + @@ -65403,6 +65408,37 @@ + + + + + + + + + + + + +

+ ganz elegant erst mal auf TheadLifecycle downcasten +

+ +
+ + + + + +

+ was „rein zufällig“ hier in diesem nested scope in ThreadLifecycle erlaubt und auch sicher ist, denn hier haben wir noch Zugriff auf die protected geerbten Policy-Klassen... +

+ +
+
+ + + @@ -66022,9 +66058,12 @@ - + + + +