Library: allow to bind a member function into self-managed thread

Oh my.
Yet another hideously complex problem and workaround...

Since a week I am like "almost done"
This commit is contained in:
Fischlurch 2023-10-11 13:21:08 +02:00
parent 42eba8425a
commit f6a6b0b68f
3 changed files with 258 additions and 15 deletions

View file

@ -360,6 +360,27 @@ namespace lib {
}
};
template<class TAR>
struct InstancePlaceholder { };
template<class W, class INVO>
inline INVO
lateBindInstance (W&, INVO invocation)
{
return invocation;
}
template<class W, class F, class TAR, typename...ARGS>
inline auto
lateBindInstance (W& instance, const tuple<F, InstancePlaceholder<TAR>, ARGS...> invocation)
{
auto splice = [&instance](auto f, auto&, auto ...args)
{
TAR* instancePtr = static_cast<TAR*> (&instance);
return tuple{move(f), instancePtr, move(args)...};
};
return std::apply (splice, invocation);
}
/**
* Policy-based configuration of thread lifecycle
@ -374,6 +395,7 @@ namespace lib {
void
invokeThreadFunction (ARGS&& ...args)
{
if (not Policy::isLive()) return;
Policy::handle_begin_thread();
Policy::markThreadStart();
Policy::perform_thread_function (forward<ARGS> (args)...);
@ -395,6 +417,19 @@ namespace lib {
{ }
public:
/**
* Build the invocation tuple, using #invokeThreadFunction
* to delegate to the user-provided functor and arguments
*/
template<class W, class...INVO>
static auto
buildInvocation (W& wrapper, tuple<INVO...>&& invocation)
{ //the thread-main function
return tuple_cat (tuple{&ThreadLifecycle::invokeThreadFunction<INVO...>
, &wrapper} // passing the wrapper as instance-this
,move (invocation)); //...invokeThreadFunction() in turn delegates
} // to the user-provided thread-operation
/**
* Build a λ actually to launch the given thread operation later,
* after the thread-wrapper-object is fully initialised.
@ -413,13 +448,12 @@ namespace lib {
buildLauncher (INVO&& ...args)
{
tuple<decay_t<INVO>...> argCopy{forward<INVO> (args)...};
return [invocation = move(argCopy)] //Note: functor+args bound by-value into the λ
return [invocation = move(argCopy)]// Note: functor+args bound by-value into the λ
(ThreadLifecycle& wrapper)
{ //the thread-main function
wrapper.launchThread (tuple_cat (tuple{&ThreadLifecycle::invokeThreadFunction<decay_t<INVO>...>
, &wrapper} // passing the wrapper as instance-this
,move (invocation))); //...invokeThreadFunction() in turn delegates
}; // to the user-provided thread-operation
{ // special treatment for launchDetached
auto boundInvocation = lateBindInstance (wrapper, move (invocation));
wrapper.launchThread (buildInvocation (wrapper, move(boundInvocation)));
};
}
@ -743,6 +777,30 @@ namespace lib {
.threadID (threadID));
}
/** Special variant bind a member function of the subclass into the autonomous thread */
template<class TAR, typename...ARGS>
inline void
launchDetached (string const& threadID, void (TAR::*memFun) (ARGS...), ARGS ...args)
{
using Launch = typename TAR::Launch;
launchDetached<TAR> (Launch{std::move (memFun)
,thread::InstancePlaceholder<TAR>{}
,forward<ARGS> (args)...
}
.threadID (threadID));
}
/** Special variant without explicitly given thread-ID */
template<class TAR, typename...ARGS>
inline void
launchDetached (void (TAR::*memFun) (ARGS...), ARGS ...args)
{
return launchDetached (util::joinDash (lib::meta::typeSymbol<TAR>(), args...)
,memFun
,forward<ARGS> (args)...
);
}
} // namespace lib
#endif /*LIB_THREAD_H*/

View file

@ -26,7 +26,9 @@
#include "lib/test/run.hpp"
#include "lib/test/testdummy.hpp"
#include "lib/thread.hpp"
#include "lib/test/diagnostic-output.hpp"///////////////////TODO
#include <atomic>
#include <chrono>
@ -83,22 +85,30 @@ namespace test{
void
verifyMemoryManagement()
{
UNIMPLEMENTED ("verify thread manages itself");
struct TestThread
: Thread
: ThreadHookable
{
using Thread::Thread;
using ThreadHookable::ThreadHookable;
uint local{0};
Dummy watcher;
void
doIt (uint a, uint b) ///< the actual operation running in a separate thread
doIt (int extra)
{
uint sum = a + b;
// sleep_for (microseconds{sum}); // Note: explicit random delay before local store
local = sum;
watcher.setVal (extra);
SHOW_EXPR(extra)
sleep_for (5ms);
SHOW_EXPR("bye")
}
};
//
CHECK (0 == Dummy::checksum());
//
launchDetached<TestThread> (&TestThread::doIt, 55);
CHECK (0 < Dummy::checksum());
sleep_for (10ms);
CHECK (0 == Dummy::checksum());
}
};

View file

@ -65342,9 +65342,122 @@
<node CREATED="1696965933149" ID="ID_1573026703" MODIFIED="1696965943299" TEXT="damit sind alle Probleme auf einmal erschlagen!"/>
<node CREATED="1696965943955" ID="ID_1421528010" MODIFIED="1696965960462" TEXT="der Code der Callbacks steht direkt neben dem &#xbb;freien&#xab; Heap-new"/>
<node CREATED="1696965962769" ID="ID_250643272" MODIFIED="1696965971748" TEXT="die down-cast ist nun sicher"/>
<node CREATED="1696965987578" ID="ID_1670280316" MODIFIED="1696965998650" TEXT="man kann beliebige Subtypen verwenden"/>
<node COLOR="#5b280f" CREATED="1696965987578" ID="ID_1670280316" MODIFIED="1696976650429" TEXT="man kann beliebige Subtypen verwenden">
<arrowlink COLOR="#f73362" DESTINATION="ID_1651696265" ENDARROW="Default" ENDINCLINATION="-61;-30;" ID="Arrow_ID_120849360" STARTARROW="None" STARTINCLINATION="283;19;"/>
<icon BUILTIN="closed"/>
</node>
<node CREATED="1696965973496" ID="ID_1562329556" MODIFIED="1696965986393" TEXT="und eine sonderbare spezial-Policy f&#xe4;llt komplett weg"/>
<node CREATED="1696965999636" ID="ID_1837122164" MODIFIED="1696966015867" TEXT="es gibt keine spezielle Thread-Klasse mehr, die man &#xbb;nicht verwenden&#xab; soll"/>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1696976539898" ID="ID_57182560" MODIFIED="1696976562311" TEXT="ABER: kann mit diesem Design keine Member-Funktionen in den Thread binden">
<icon BUILTIN="broken-line"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696976568054" ID="ID_1651696265" MODIFIED="1696976852099" TEXT="das macht abgeleitete Klassen effektiv sinnlos">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
denn: grade hier f&#252;r den &#187;Autonomous Thread&#171; kommt man von au&#223;en nicht an die Addresse des Objektes ran
</p>
<ul>
<li>
die Addresse ist vor der Allokation nicht bekannt, kann daher nicht in ein Lambda gebunden werden
</li>
<li>
eine statische Funktion w&#252;rde zus&#228;tzlich noch einen Singleton-Mechanismus brauchen
</li>
<li>
ohne Zugang zum Objekt ist es aber auch sinnlos, zus&#228;tzliche Daten und Funktionen im abgeleiteten Objekt zu haben
</li>
</ul>
</body>
</html>
</richcontent>
<linktarget COLOR="#f73362" DESTINATION="ID_1651696265" ENDARROW="Default" ENDINCLINATION="-61;-30;" ID="Arrow_ID_120849360" SOURCE="ID_1670280316" STARTARROW="None" STARTINCLINATION="283;19;"/>
</node>
<node CREATED="1696976853336" ID="ID_372976819" MODIFIED="1696976877733" TEXT="das f&#xfc;hrt die Idee mit einer selbst-verwalteten Allokation ad absurdum">
<icon BUILTIN="smiley-angry"/>
</node>
<node CREATED="1696976940356" ID="ID_232121625" MODIFIED="1696977061323" TEXT="hier r&#xe4;cht sich auch das &#xbb;Zwiebelschalen-Design&#xab;: der Launch-builder ist opaque"/>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1696978145159" ID="ID_314579443" MODIFIED="1696978153755" TEXT="gibt es &#xfc;berhaupt einen Fix?">
<icon BUILTIN="help"/>
<node COLOR="#5b280f" CREATED="1696978155858" ID="ID_229604383" MODIFIED="1696978809501" TEXT="&#xfc;bler Hack: Fake-Allokation + placement-New">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
Wenn wir es irgendwie schaffen k&#246;nnten, das Objekt schon auf den Heap zu allozieren, aber zu verhindern, da&#223; es &#8222;fliegt&#8220; (bzw. wirklich konstruiert wird). Dann w&#252;&#223;ten wir die Addresse. Danach w&#252;rden wir ein placement-New in die Allokation machen, und k&#246;nnten diesem <i>tats&#228;chlichen </i>Konstruktor-Aufruf <i>ganz vertr&#228;umt </i>den richtigen Instanz-Pointer mitgeben. Vorraussetzung w&#228;re allerdings, da&#223; sich diese manipulierte Allokation sp&#228;ter ganz gew&#246;hnlich per operator delete wieder entfernen lie&#223;e. Das w&#228;re dann entweder ein fragiler Hack, der sich auf Plattform-Interna abst&#252;tzt, oder es w&#228;re ein custom-operator-new im Stil der 90er-Jahre
</p>
</body>
</html>
</richcontent>
<icon BUILTIN="closed"/>
</node>
<node CREATED="1696978523249" ID="ID_1356286451" MODIFIED="1697023022905" TEXT="einen Platzhalter sp&#xe4;t an die eigentliche Instanz binden">
<richcontent TYPE="NOTE">&lt;html&gt;
&lt;head&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;
Daf&amp;#252;r br&amp;#228;uchte ich eine Stelle, an der die tats&amp;#228;chliche Instanz bereits
bekannt ist, aber die Invocation nochin generischer Form vorliegt. In
einem solchen Kontext k&amp;#246;nnte man dann einen Marker-Typ erkennen und an
die tats&amp;#228;chliche Instanz binden.
&lt;/p&gt;
&lt;p&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;u&gt;&amp;#55357;&amp;#56481; Und in der Tat&lt;/u&gt;: diesen Kontext g&amp;#228;be es in der
Hilfsfunktion buildLauncher()
&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;
</richcontent>
<arrowlink COLOR="#3551b4" DESTINATION="ID_1017963709" ENDARROW="Default" ENDINCLINATION="8;-31;" ID="Arrow_ID_855336150" STARTARROW="None" STARTINCLINATION="-132;6;"/>
<icon BUILTIN="forward"/>
<node CREATED="1696978822594" ID="ID_1410342065" MODIFIED="1696979058894" TEXT="diese L&#xf6;sung w&#xe4;re zuverl&#xe4;ssig, sicher und effizient">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<ul>
<li>
zuverl&#228;ssig: sie wird zur Compile-Zeit eingebunden und ist &#252;ber das Typsystem abgesichert
</li>
<li>
sicher: sie baut auf einen speziellen Marker-Typ auf &#8212; wenn er fehlt, passiert nichts an der Stelle
</li>
<li>
effizient: da sie in einer Funktionstemplate-Instantiierung steht, wird sie nur im fraglichen Spezialfall getriggert
</li>
</ul>
</body>
</html>
</richcontent>
</node>
<node CREATED="1696978865091" ID="ID_1134916435" MODIFIED="1696978893087" TEXT="aber sch&#xf6;n ist sie nicht &#x2014; bestenfalls gut versteckt"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696979065849" ID="ID_1017963709" MODIFIED="1697023014065" TEXT="Umsetzung versuchen">
<linktarget COLOR="#3551b4" DESTINATION="ID_1017963709" ENDARROW="Default" ENDINCLINATION="8;-31;" ID="Arrow_ID_855336150" SOURCE="ID_1356286451" STARTARROW="None" STARTINCLINATION="-132;6;"/>
<icon BUILTIN="yes"/>
<node CREATED="1697022864978" ID="ID_360386085" MODIFIED="1697022989269" TEXT="im Prinzip &#x201e;ganz einfach&#x201c;">
<icon BUILTIN="ksmiletris"/>
</node>
<node CREATED="1697022883830" ID="ID_996336424" MODIFIED="1697022924582" TEXT="C++17 - Trick: std::invoke verwenden mit einem &#x3bb; zum Tupel-Umbau">
<icon BUILTIN="idea"/>
</node>
<node CREATED="1697022927591" ID="ID_1968515341" MODIFIED="1697022985926" TEXT="hab ein paar Stunden gebraucht umd den &#x201e;ganz einfachen&#x201c; Aufruf richtig einzubauen">
<icon BUILTIN="smiley-angry"/>
</node>
</node>
</node>
</node>
</node>
<node CREATED="1696532404400" ID="ID_1917558827" MODIFIED="1696539443047" TEXT="Variante-2 : optional-Lifecycle">
@ -65762,6 +65875,67 @@
</body>
</html></richcontent>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1696972878105" ID="ID_880873658" MODIFIED="1696972924189">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
allerdings: abgeleitete Klassen <b>leben gef&#228;hrlich</b>
</p>
</body>
</html>
</richcontent>
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1696972938795" ID="ID_1194001800" MODIFIED="1696972960949" TEXT="wenn im Konstruktor etwas schiefgeht"/>
<node CREATED="1696972967768" ID="ID_1795585703" MODIFIED="1696972989481" TEXT="dann gelingt nicht einmal die Heap-Allokation vollst&#xe4;ndig"/>
<node CREATED="1696973111469" ID="ID_229806295" MODIFIED="1696973186990" TEXT="und der Destruktor blockt und scheitert nicht...">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
Ein normaler Thread (und ein Joinable) w&#252;rden den Aufruf des Destruktors verbieten, und damit die Applikation terminieren
</p>
</body>
</html>
</richcontent>
</node>
<node CREATED="1696972994851" ID="ID_983723928" MODIFIED="1696973016584">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
aber der Thread <i>ist schon &#8222;unterwegs&#8220;</i>
</p>
</body>
</html>
</richcontent>
<icon BUILTIN="clanbomber"/>
</node>
<node COLOR="#435e98" CREATED="1696973047334" ID="ID_1485957927" MODIFIED="1696973196674" TEXT="Mitigation: nicht in die Thread-Funktion eintreten wenn nicht isAlive()">
<icon BUILTIN="idea"/>
<node CREATED="1696973209112" ID="ID_1135397788" MODIFIED="1696973227449" TEXT="die C++ Lib schlie&#xdf;t diesen Fall eigentlich aus"/>
<node CREATED="1696973227990" ID="ID_481018550" MODIFIED="1696976336073" TEXT="aber wenn jemand explizit detach() macht..."/>
<node CREATED="1696973241644" ID="ID_1093970050" MODIFIED="1696973309225" TEXT="eigentlich m&#xfc;&#xdf;te man diesen Fall auf einen &#xbb;Autonomous Thread&#xab; einschr&#xe4;nken">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
...aber das gibt mein Framework nicht her
</p>
</body>
</html>
</richcontent>
</node>
</node>
</node>
</node>
<node CREATED="1696536197821" ID="ID_817844637" MODIFIED="1696536200313" TEXT="Variante-2">
<node CREATED="1696536201285" ID="ID_688200807" MODIFIED="1696536223893" TEXT="die Analyse f&#xfc;r Variante-1 gilt entsprechend"/>
@ -66134,6 +66308,7 @@
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696966318864" ID="ID_1955344707" MODIFIED="1696966351181" TEXT="sauberes Memory-Management belegen">
<icon BUILTIN="flag-yellow"/>
<node CREATED="1696971867646" ID="ID_1915512401" MODIFIED="1696971888573" TEXT="lib/test/testdummy.hpp &#xd83e;&#xdc7a; als Member verwenden"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696538825003" ID="ID_1787030725" MODIFIED="1696539451332" TEXT="ThreadWrapperLifecycle_test">