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.
This commit is contained in:
Fischlurch 2023-10-10 19:47:39 +02:00
parent 578af05ebd
commit 8b3f9e17cd
4 changed files with 130 additions and 62 deletions

View file

@ -128,6 +128,7 @@ namespace meta{
using Args = Types<ARGS...>;
using Sig = RET(ARGS...);
using Functor = std::function<Sig>;
enum { ARITY = sizeof...(ARGS) };
};
/** Specialisation to strip `noexcept` from the signature */

View file

@ -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<BAS>;
using BasePol::BasePol;
using Hook = function<void(TAR&)>;
using Self = PolicyLifecycleHook;
using Hook = function<void(Self&)>;
Hook hook_beginThread{};
Hook hook_afterThread{};
Hook hook_looseThread{};
TAR&
castInstance()
{
return static_cast<TAR*>(
static_cast<void*> (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<typename HOOK>
Launch&&
atEnd (HOOK&& hook)
atExit (HOOK&& hook)
{
return addHook (&Policy::hook_afterThread, forward<HOOK> (hook));
}
@ -503,11 +499,29 @@ namespace lib {
Launch&&
addHook (FUN Policy::*storedHook, HOOK&& hook)
{
return addLayer ([storedHook, hook = forward<HOOK>(hook)]
static_assert(1 == _Fun<FUN>::ARITY);
static_assert(1 >= _Fun<HOOK>::ARITY);
using Arg = typename _Fun<FUN>::Args::List::Head;
FUN adapted;
if constexpr (0 == _Fun<HOOK>::ARITY)
{
adapted = [hook = forward<HOOK>(hook)](Arg){ hook(); };
}
else
{
using Target = typename _Fun<HOOK>::Args::List::Head;
adapted = [hook = forward<HOOK>(hook)]
(Arg& threadWrapper)
{
ThreadLifecycle& base = static_cast<ThreadLifecycle&> (threadWrapper);
Target& target = static_cast<Target&> (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<thread::PolicyLifecycleHook>
{
public:
using ThreadLifecycle::ThreadLifecycle;
};
/************************************************************************//**
* Special configuration for a »fire-and-forget«-Thread.
* @internal this class is meant for subclassing. Start with #launchDetached()

View file

@ -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 "<<rand()%scope <<endl;
}
, 47)
.threadID("haha")};
UNIMPLEMENTED ("demonstrate state change");
}
@ -106,8 +99,35 @@ SHOW_EXPR(offset)
* the thread's lifecycle state.
*/
void
verifyExplicitLifecycleState()
verifyThreadLifecycleHooks()
{
atomic_uint stage{0};
ThreadHookable thread{ThreadHookable::Launch([]{ sleep_for (5ms); })
.threadID("hooked thread")
.atStart([&]{ stage = 1; })
.atExit ([&]{ stage = 2; })};
CHECK (thread);
CHECK (0 == stage);
sleep_for (1ms);
CHECK (thread);
CHECK (1 == stage);
while (thread) yield();
CHECK (not thread);
CHECK (2 == stage);
}
/**
* @test verify a special setup to start a thread explicitly and to track
* the thread's lifecycle state.
*/
void
demonstrateExplicitThreadLifecycle()
{
UNIMPLEMENTED ("demonstrate state change");
struct TestThread
: Thread
{
@ -119,39 +139,10 @@ SHOW_EXPR(offset)
doIt (uint a, uint b) ///< the actual operation running in a separate thread
{
uint sum = a + b;
sleep_for (microseconds{sum}); // Note: explicit random delay before local store
// sleep_for (microseconds{sum}); // Note: explicit random delay before local store
local = sum;
}
};
// prepare Storage for these objects (not created yet)
lib::ScopedCollection<TestThread> 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
}
};

View file

@ -65242,7 +65242,7 @@
<icon BUILTIN="forward"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696898745626" ID="ID_1404625774" MODIFIED="1696898766166" TEXT="mu&#xdf; einen gewaltsamen Upcast machen">
<node COLOR="#435e98" CREATED="1696898745626" ID="ID_1404625774" MODIFIED="1696958131450" TEXT="mu&#xdf; einen gewaltsamen Upcast machen">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1696898767838" ID="ID_982197270" MODIFIED="1696898773034" TEXT="und zwar &#xfc;ber void*"/>
<node CREATED="1696898773597" ID="ID_1027296232" MODIFIED="1696898787176" TEXT="weil wir einmal protected-Inheritance verwenden"/>
@ -65262,6 +65262,10 @@
</html></richcontent>
<icon BUILTIN="clanbomber"/>
</node>
<node COLOR="#435e98" CREATED="1696958144365" ID="ID_1650867169" MODIFIED="1696958397510" TEXT="stattdessen: sichere L&#xf6;sung &#xfc;ber den Launch-config builder">
<arrowlink COLOR="#3bacb8" DESTINATION="ID_1240123723" ENDARROW="Default" ENDINCLINATION="561;0;" ID="Arrow_ID_187329351" STARTARROW="None" STARTINCLINATION="386;18;"/>
<icon BUILTIN="idea"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696532299942" ID="ID_596357210" MODIFIED="1696898738780" TEXT="die jeweilige Front-End-Klasse nur in der Factory definieren">
<icon BUILTIN="yes"/>
@ -65386,7 +65390,8 @@
<icon BUILTIN="stop-sign"/>
</node>
</node>
<node CREATED="1696934198943" ID="ID_737180308" MODIFIED="1696934210349" TEXT="besser: funktionale Erweiterungspunkte">
<node COLOR="#435e98" CREATED="1696934198943" ID="ID_737180308" MODIFIED="1696958409322" TEXT="besser: funktionale Erweiterungspunkte">
<icon BUILTIN="yes"/>
<node CREATED="1696934212695" ID="ID_1626568717" MODIFIED="1696934238138" TEXT="diese werden als std::function gespeichert"/>
<node CREATED="1696934261703" ID="ID_266607477" MODIFIED="1696934280168" TEXT="und aus entsprechenden Policy-hooks heraus aufgerufen"/>
<node CREATED="1696934280923" ID="ID_787857879" MODIFIED="1696934285592" TEXT="Hooks">
@ -65395,7 +65400,7 @@
<linktarget COLOR="#fdfcc6" DESTINATION="ID_1004228427" ENDARROW="Default" ENDINCLINATION="-114;10;" ID="Arrow_ID_936715522" SOURCE="ID_1539716722" STARTARROW="None" STARTINCLINATION="116;8;"/>
</node>
<node CREATED="1696529836760" ID="ID_1153306127" MODIFIED="1696934476853" TEXT="zus&#xe4;tzlich: &#x27ff; handle_loose_thread">
<linktarget COLOR="#fdfcc6" DESTINATION="ID_1153306127" ENDARROW="Default" ENDINCLINATION="-114;10;" ID="Arrow_ID_880044186" SOURCE="ID_1868637331" STARTARROW="None" STARTINCLINATION="263;12;"/>
<linktarget COLOR="#fdfcc6" DESTINATION="ID_1153306127" ENDARROW="Default" ENDINCLINATION="-341;27;" ID="Arrow_ID_880044186" SOURCE="ID_1868637331" STARTARROW="None" STARTINCLINATION="263;12;"/>
<node CREATED="1696933886929" HGAP="25" ID="ID_796423082" MODIFIED="1696933932976" TEXT="(wird aufgerufen aus dem Destruktor &#x2014; falls der Thread noch l&#xe4;uft)" VSHIFT="7">
<font NAME="SansSerif" SIZE="11"/>
<icon BUILTIN="info"/>
@ -65403,6 +65408,37 @@
</node>
</node>
</node>
<node COLOR="#338800" CREATED="1696958202741" ID="ID_1577864867" MODIFIED="1696958412378" TEXT="diese bereits im Launch-config-builder adaptieren">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1696958220491" ID="ID_1240123723" MODIFIED="1696958388855" TEXT="das l&#xf6;st das Problem mit dem downcast auf einen Subklassen-Typ">
<linktarget COLOR="#3bacb8" DESTINATION="ID_1240123723" ENDARROW="Default" ENDINCLINATION="561;0;" ID="Arrow_ID_187329351" SOURCE="ID_1650867169" STARTARROW="None" STARTINCLINATION="386;18;"/>
<icon BUILTIN="button_ok"/>
</node>
<node CREATED="1696958235467" ID="ID_1922263135" MODIFIED="1696958334910">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
<i>ganz elegant </i>erst mal auf TheadLifecycle downcasten
</p>
</body>
</html></richcontent>
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
was &#8222;rein zuf&#228;llig&#8220; hier in diesem nested scope in ThreadLifecycle erlaubt und auch sicher ist, denn hier haben wir noch Zugriff auf die protected geerbten Policy-Klassen...
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1696958336516" ID="ID_564028687" MODIFIED="1696958350950" TEXT="im zweiten Schritt von dort dann auf einen beliebigen Subtyp"/>
<node CREATED="1696958351770" ID="ID_174542726" MODIFIED="1696958368388" TEXT="den konkret vom gegebenen &#x3bb; erwarteten Argument-Typ verwenden"/>
</node>
</node>
<node CREATED="1696532838782" ID="ID_46766207" MODIFIED="1696532858447" TEXT="der Konflikt im Lebensdauer-Thema ist damit beigelegt">
<node CREATED="1696532860011" ID="ID_132397075" MODIFIED="1696532867737" TEXT="bisher hatten wir hier einen Zielkonflikt"/>
@ -66022,9 +66058,12 @@
<node CREATED="1696538612261" ID="ID_661367597" MODIFIED="1696538614839" TEXT="f&#xfc;r Variante-2">
<node CREATED="1696933810166" ID="ID_906750178" MODIFIED="1696933829261" TEXT="stelle Storage bereit als std::function"/>
<node CREATED="1696529718984" ID="ID_1868637331" MODIFIED="1696934476853" TEXT="die schon definierten Hooks + zus&#xe4;tzlichen Erweiterungspunkt">
<arrowlink COLOR="#fdfcc6" DESTINATION="ID_1153306127" ENDARROW="Default" ENDINCLINATION="-114;10;" ID="Arrow_ID_880044186" STARTARROW="None" STARTINCLINATION="263;12;"/>
<arrowlink COLOR="#fdfcc6" DESTINATION="ID_1153306127" ENDARROW="Default" ENDINCLINATION="-341;27;" ID="Arrow_ID_880044186" STARTARROW="None" STARTINCLINATION="263;12;"/>
<icon BUILTIN="idea"/>
</node>
<node COLOR="#338800" CREATED="1696958434135" ID="ID_1569167047" LINK="#ID_1240123723" MODIFIED="1696958461524" TEXT="Adaptierung auf beliebigen Subtyp automatisch">
<icon BUILTIN="button_ok"/>
</node>
</node>
</node>
<node COLOR="#338800" CREATED="1696690988844" ID="ID_480585061" MODIFIED="1696861459249" TEXT="das Race-Problem addressieren">