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:
parent
578af05ebd
commit
8b3f9e17cd
4 changed files with 130 additions and 62 deletions
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -65242,7 +65242,7 @@
|
|||
<icon BUILTIN="forward"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696898745626" ID="ID_1404625774" MODIFIED="1696898766166" TEXT="muß einen gewaltsamen Upcast machen">
|
||||
<node COLOR="#435e98" CREATED="1696898745626" ID="ID_1404625774" MODIFIED="1696958131450" TEXT="muß einen gewaltsamen Upcast machen">
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
<node CREATED="1696898767838" ID="ID_982197270" MODIFIED="1696898773034" TEXT="und zwar ü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ösung ü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ätzlich: ⟿ 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 — falls der Thread noch lä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ö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 „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...
|
||||
</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 λ 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ü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ä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">
|
||||
|
|
|
|||
Loading…
Reference in a new issue