Library: test coverage for lifecycle management

Add a complete demonstration for a setup akin to what we use
for the Session thread: a threaded component which manages itself
but also exposes an external interface, which is opened/closed alongside
This commit is contained in:
Fischlurch 2023-10-11 22:02:52 +02:00
parent 7b25609896
commit 29b9126c26
5 changed files with 100 additions and 46 deletions

View file

@ -225,6 +225,20 @@ namespace lib {
void handle_begin_thread() { } ///< called immediately at start of thread
void handle_after_thread() { } ///< called immediately before end of thread
void handle_loose_thread() { } ///< called when destroying wrapper on still running thread
/**
* allow to detach explicitly independent from thread-function's state.
* @warning this function is borderline dangerous; it might be acceptable
* in a situation where the thread totally manages itself and the
* thread object is maintained in a unique_ptr. You must ensure that
* the thread function does not touch anything in the wrapper object
* after that point and only uses storage within its own scope.
*/
void detach_thread_from_wrapper()
{
if (isLive())
threadImpl_.detach();
}
};
@ -362,6 +376,7 @@ namespace lib {
};
/**
* Policy-based configuration of thread lifecycle
*/
@ -456,6 +471,13 @@ namespace lib {
: launch{buildLauncher (forward<FUN>(threadFunction), forward<ARGS>(args)...)}
{ }
template<class TAR, typename...ARGS>
Launch (RES (TAR::*memFun) (ARGS...), ARGS ...args) ///< ctor variant to bind a member function
: Launch{move (memFun)
,lib::meta::InstancePlaceholder<TAR>{}
,forward<ARGS> (args)... }
{ }
Launch&&
threadID (string const& threadID)
{
@ -737,8 +759,7 @@ namespace lib {
})
.onOrphan([](thread::ThreadWrapper& wrapper)
{
if (wrapper.isLive())
wrapper.threadImpl_.detach();
wrapper.detach_thread_from_wrapper();
})};
// Note: allocation tossed on the heap deliberately
} // The thread-function will pick up and manage *this

View file

@ -231,6 +231,8 @@ namespace control {
* any operation running in the Session thread is
* started from here. When this loop terminates,
* the »session subsystem« shuts down.
* @note the callback \a notifyEnd is typically bound
* to invoke SteamDispatcher::endRunningLoopState().
*/
void
runSessionThread (Subsys::SigTerm notifyEnd)

View file

@ -28,11 +28,10 @@
#include "lib/test/run.hpp"
#include "lib/meta/function.hpp"
#include "lib/meta/tuple-helper.hpp"
#include "lib/test/test-helper.hpp"
#include "lib/test/testdummy.hpp"
#include "lib/format-cout.hpp"
#include "lib/format-util.hpp"
#include "lib/test/diagnostic-output.hpp"
#include "lib/rational.hpp"
#include "lib/util.hpp"
#include <tuple>

View file

@ -27,31 +27,27 @@
#include "lib/test/run.hpp"
#include "lib/thread.hpp"
#include "lib/iter-explorer.hpp"
#include "lib/scoped-collection.hpp"
#include "lib/test/microbenchmark.hpp"
#include "lib/test/diagnostic-output.hpp"
#include "lib/test/testdummy.hpp"
#include <atomic>
#include <chrono>
#include <memory>
using test::Test;
using lib::explore;
using lib::test::Dummy;
using std::atomic_uint;
using std::this_thread::yield;
using std::this_thread::sleep_for;
using namespace std::chrono_literals;
using std::chrono::system_clock;
using std::unique_ptr;
namespace lib {
namespace test{
namespace { // test parameters
const uint NUM_THREADS = 200;
const uint REPETITIONS = 10;
namespace {
using CLOCK_SCALE = std::micro; // Results are in µ-sec
}
@ -96,11 +92,11 @@ namespace test{
double offset = Dur{threadStart - afterCtor}.count();
CHECK (offset > 0);
} // Note: in practice we see here values > 100µs
// but in theory the thread might even overtake the launcher
// but in theory the thread might even overtake the launcher
/**
* @test attach user provided callback hooks to the thread lifecycle.
/** @test attach user provided callback hooks to the thread lifecycle.
*/
void
verifyThreadLifecycleHooks()
@ -124,29 +120,58 @@ namespace test{
/**
* @test verify a special setup to start a thread explicitly and to track
* the thread's lifecycle state.
* @test verify a special setup to start a thread explicitly
* and to track the thread's lifecycle state.
* - use a component to encapsulate the thread
* - this TestThread component is managed in a `unique_ptr`
* - thus it is explicitly possible to be _not_ in _running state_
* - when starting the TestThread, a lifecycle callback is bound
* - at termination this callback will clear the unique_ptr
* - thus allocation and _running state_ is tied to the lifecycle
*/
void
demonstrateExplicitThreadLifecycle()
{
UNIMPLEMENTED ("demonstrate state change");
struct TestThread
: Thread
: ThreadHookable
{
using Thread::Thread;
using ThreadHookable::ThreadHookable;
uint local{0};
atomic_uint processVal{23};
void
doIt (uint a, uint b) ///< the actual operation running in a separate thread
doIt (uint haveFun)
{
uint sum = a + b;
// sleep_for (microseconds{sum}); // Note: explicit random delay before local store
local = sum;
sleep_for (100us);
processVal = haveFun;
sleep_for (5ms);
}
};
}
// Note the Dummy member allows to watch instance lifecycle
CHECK (0 == Dummy::checksum());
// the frontEnd allows to access the TestThread component
// and also represents the running state
unique_ptr<TestThread> frontEnd;
CHECK (not frontEnd); // obviously not running yet
// start the thread and wire lifecycle callbacks
frontEnd.reset (new TestThread{
TestThread::Launch{&TestThread::doIt, 55u}
.atExit([&]{ frontEnd.reset(); })
.onOrphan([](thread::ThreadWrapper& wrapper)
{ wrapper.detach_thread_from_wrapper(); })
});
CHECK (frontEnd); // thread now marked as running
CHECK (23 == frontEnd->processVal); // this value was set by the ctor in this thread
sleep_for (1ms); // wait for the thread function to become active
CHECK (55 == frontEnd->processVal); // changed by thread function
sleep_for (10ms);
CHECK (not frontEnd); // meanwhile thread has finished
} // and also cleared the front-end from the `atExit`-hook
};

View file

@ -65009,9 +65009,9 @@
<node COLOR="#338800" CREATED="1695597447169" ID="ID_35947260" MODIFIED="1695859231223" TEXT="Fehlerbehandlung">
<icon BUILTIN="button_ok"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696468379848" ID="ID_1146069423" MODIFIED="1696541583707" TEXT="Erg&#xe4;nzung: autonomous Thread">
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1696468379848" ID="ID_1146069423" MODIFIED="1697054187170" TEXT="Erg&#xe4;nzung: autonomous Thread">
<linktarget COLOR="#bc3562" DESTINATION="ID_1146069423" ENDARROW="Default" ENDINCLINATION="444;909;" ID="Arrow_ID_516910789" SOURCE="ID_137837629" STARTARROW="None" STARTINCLINATION="-1280;-32;"/>
<icon BUILTIN="flag-yellow"/>
<icon BUILTIN="pencil"/>
<node COLOR="#435e98" CREATED="1696468398477" ID="ID_1775921981" MODIFIED="1696966299356" TEXT="neue Anforderung: komplett abgekoppelt">
<icon BUILTIN="yes"/>
<node CREATED="1696468424018" ID="ID_1127252428" MODIFIED="1696468584146" TEXT="ergibt sich aus dem Protokoll der C++14 - Threads">
@ -65045,7 +65045,7 @@
<node CREATED="1696468151894" ID="ID_1613034704" MODIFIED="1696468650111" TEXT="der Thread-Funktor mu&#xdf; das Thread-Objekt als selbst halten"/>
<node CREATED="1696468197640" ID="ID_1523718397" MODIFIED="1696468221818" TEXT="der Aufruf-context gibt jeden Bezug dazu auf"/>
</node>
<node COLOR="#435e98" CREATED="1696522906584" ID="ID_1207549127" MODIFIED="1696525458994" TEXT="ist dieser Ansatz sinnvoll?">
<node COLOR="#435e98" CREATED="1696522906584" FOLDED="true" ID="ID_1207549127" MODIFIED="1696525458994" TEXT="ist dieser Ansatz sinnvoll?">
<icon BUILTIN="help"/>
<node CREATED="1696523014919" ID="ID_385522841" MODIFIED="1696523029472" TEXT="es ginge auch anders: optional&lt;Thread&gt;">
<icon BUILTIN="idea"/>
@ -65115,7 +65115,7 @@
<icon BUILTIN="button_ok"/>
</node>
</node>
<node COLOR="#435e98" CREATED="1696528863266" ID="ID_1924635467" MODIFIED="1696966145777" TEXT="soll aber sauberes Design sein">
<node COLOR="#435e98" CREATED="1696528863266" FOLDED="true" ID="ID_1924635467" MODIFIED="1696966145777" TEXT="soll aber sauberes Design sein">
<icon BUILTIN="yes"/>
<node CREATED="1696528894446" ID="ID_658666182" MODIFIED="1696528900161" TEXT="das hei&#xdf;t">
<node CREATED="1696528900773" ID="ID_1777061932" MODIFIED="1696528909994" TEXT="in das Policy-Schema integriert"/>
@ -65157,7 +65157,7 @@
</html></richcontent>
</node>
</node>
<node COLOR="#435e98" CREATED="1696529335091" ID="ID_1772510094" MODIFIED="1696529606844" TEXT="Erweiterungspunkt in der Policy ausbauen">
<node COLOR="#435e98" CREATED="1696529335091" FOLDED="true" ID="ID_1772510094" MODIFIED="1696529606844" TEXT="Erweiterungspunkt in der Policy ausbauen">
<icon BUILTIN="forward"/>
<node CREATED="1696529349169" ID="ID_722473249" MODIFIED="1696529353436" TEXT="und zwar den zum Thread-Ende">
<node CREATED="1696529438893" ID="ID_842811550" MODIFIED="1696529439969" TEXT="handle_end_of_thread">
@ -65211,7 +65211,7 @@
<arrowlink COLOR="#fdfcc6" DESTINATION="ID_1004228427" ENDARROW="Default" ENDINCLINATION="-114;10;" ID="Arrow_ID_936715522" STARTARROW="None" STARTINCLINATION="116;8;"/>
<icon BUILTIN="idea"/>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1696529962087" ID="ID_683965677" MODIFIED="1696531663483" TEXT="ownership wird hier gekapert">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1696529962087" FOLDED="true" ID="ID_683965677" MODIFIED="1696531663483" TEXT="ownership wird hier gekapert">
<icon BUILTIN="clanbomber"/>
<node CREATED="1696531683840" ID="ID_1453668397" MODIFIED="1696531729036" TEXT="w&#xfc;rde zu schwer diagnostizierbaren Fehler f&#xfc;hren, wenn Objekt anderweitig erzeugt wurde">
<icon BUILTIN="messagebox_warning"/>
@ -65268,7 +65268,7 @@
</node>
</node>
</node>
<node COLOR="#5b280f" CREATED="1696897248135" ID="ID_433063268" MODIFIED="1696965815605" TEXT="Subklassen erm&#xf6;glichen?">
<node COLOR="#5b280f" CREATED="1696897248135" FOLDED="true" ID="ID_433063268" MODIFIED="1696965815605" TEXT="Subklassen erm&#xf6;glichen?">
<icon BUILTIN="help"/>
<icon BUILTIN="button_cancel"/>
<node CREATED="1696897270002" ID="ID_944165024" MODIFIED="1696897420063">
@ -65328,7 +65328,7 @@
</node>
</node>
</node>
<node COLOR="#338800" CREATED="1696965775488" ID="ID_1909867794" MODIFIED="1696965923694">
<node COLOR="#338800" CREATED="1696965775488" FOLDED="true" ID="ID_1909867794" MODIFIED="1696965923694">
<richcontent TYPE="NODE"><html>
<head/>
<body>
@ -65348,7 +65348,7 @@
</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 COLOR="#435e98" CREATED="1696976539898" ID="ID_57182560" MODIFIED="1697050703737" TEXT="ABER: kann mit diesem Design keine Member-Funktionen in den Thread binden">
<node COLOR="#435e98" CREATED="1696976539898" FOLDED="true" ID="ID_57182560" MODIFIED="1697050703737" TEXT="ABER: kann mit diesem Design keine Member-Funktionen in den Thread binden">
<linktarget COLOR="#5d6faa" DESTINATION="ID_57182560" ENDARROW="Default" ENDINCLINATION="-230;791;" ID="Arrow_ID_1090153547" SOURCE="ID_92765841" STARTARROW="None" STARTINCLINATION="772;-34;"/>
<icon BUILTIN="broken-line"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696976568054" ID="ID_1651696265" MODIFIED="1696976852099" TEXT="das macht abgeleitete Klassen effektiv sinnlos">
@ -65475,7 +65475,7 @@
</node>
</node>
</node>
<node CREATED="1696532404400" ID="ID_1917558827" MODIFIED="1696539443047" TEXT="Variante-2 : optional-Lifecycle">
<node COLOR="#435e98" CREATED="1696532404400" ID="ID_1917558827" MODIFIED="1697054094884" TEXT="Variante-2 : optional-Lifecycle">
<linktarget COLOR="#898bab" DESTINATION="ID_1917558827" ENDARROW="Default" ENDINCLINATION="-1027;99;" ID="Arrow_ID_1553665185" SOURCE="ID_104020495" STARTARROW="None" STARTINCLINATION="532;77;"/>
<node CREATED="1696532578904" ID="ID_1181618773" MODIFIED="1696532615943" TEXT="erlaubt explizit einen noch nicht / nicht mehr laufenden Zustand"/>
<node CREATED="1696532628426" ID="ID_295807620" MODIFIED="1696532638596" TEXT="in der Tat: das wird manchmal gebraucht"/>
@ -65489,7 +65489,7 @@
</body>
</html></richcontent>
</node>
<node COLOR="#5b280f" CREATED="1696532676404" ID="ID_91266848" MODIFIED="1696934197232" TEXT="Front-End">
<node COLOR="#5b280f" CREATED="1696532676404" FOLDED="true" ID="ID_91266848" MODIFIED="1697054120642" TEXT="Front-End">
<icon BUILTIN="button_cancel"/>
<node CREATED="1696532680835" ID="ID_553798887" MODIFIED="1696536376316">
<richcontent TYPE="NODE"><html>
@ -65546,7 +65546,7 @@
</node>
</node>
</node>
<node COLOR="#338800" CREATED="1696958202741" ID="ID_1577864867" MODIFIED="1696958412378" TEXT="diese bereits im Launch-config-builder adaptieren">
<node COLOR="#338800" CREATED="1696958202741" FOLDED="true" 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;"/>
@ -65808,7 +65808,7 @@
</node>
</node>
</node>
<node COLOR="#435e98" CREATED="1696533317806" ID="ID_641021700" MODIFIED="1696966141682" TEXT="Sicherheit">
<node COLOR="#435e98" CREATED="1696533317806" FOLDED="true" ID="ID_641021700" MODIFIED="1696966141682" TEXT="Sicherheit">
<icon BUILTIN="yes"/>
<node CREATED="1696533340931" ID="ID_1317798504" MODIFIED="1696533569753" TEXT="Manipulations-Schutz &#x27f9; wird auf vern&#xfc;nftige Nutzung zur&#xfc;ckgef&#xfc;hrt">
<richcontent TYPE="NOTE"><html>
@ -66208,7 +66208,7 @@
<node CREATED="1696538713759" ID="ID_743139495" MODIFIED="1696538713759" TEXT="handle_after_thread"/>
<node CREATED="1696961378726" ID="ID_1213403952" MODIFIED="1696961379986" TEXT="handle_loose_thread"/>
</node>
<node CREATED="1696538607372" ID="ID_479618351" MODIFIED="1696538611095" TEXT="f&#xfc;r Variante-1">
<node COLOR="#435e98" CREATED="1696538607372" FOLDED="true" ID="ID_479618351" MODIFIED="1697054175018" TEXT="f&#xfc;r Variante-1">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1696890808413" ID="ID_569216542" LINK="#ID_1183918146" MODIFIED="1696890916407" TEXT="es zeigt sich: ein smart-ptr l&#xf6;st das Problem gar nicht">
<icon BUILTIN="stop-sign"/>
</node>
@ -66224,7 +66224,7 @@
<node CREATED="1696966232300" ID="ID_607167636" MODIFIED="1696966249766" TEXT="nur spezielles front-End"/>
</node>
</node>
<node CREATED="1696538612261" ID="ID_661367597" MODIFIED="1696538614839" TEXT="f&#xfc;r Variante-2">
<node COLOR="#435e98" CREATED="1696538612261" FOLDED="true" ID="ID_661367597" MODIFIED="1697054174113" 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="-341;27;" ID="Arrow_ID_880044186" STARTARROW="None" STARTINCLINATION="263;12;"/>
@ -66235,7 +66235,7 @@
</node>
</node>
</node>
<node COLOR="#338800" CREATED="1696690988844" ID="ID_480585061" MODIFIED="1696861459249" TEXT="das Race-Problem addressieren">
<node COLOR="#338800" CREATED="1696690988844" FOLDED="true" ID="ID_480585061" MODIFIED="1696861459249" TEXT="das Race-Problem addressieren">
<icon BUILTIN="button_ok"/>
<node CREATED="1696690998156" ID="ID_394310225" MODIFIED="1696691010645" TEXT="der Thread wird erst aus dem Konstruktor-Rumpf gestartet"/>
<node CREATED="1696691012041" ID="ID_1592624785" MODIFIED="1696691021403" TEXT="daf&#xfc;r gibt es eine neue Policy-Funktion"/>
@ -66303,7 +66303,8 @@
<node CREATED="1696893059260" ID="ID_109825250" MODIFIED="1696893063103" TEXT="Variante-2"/>
</node>
</node>
<node CREATED="1696538743210" ID="ID_1370366397" MODIFIED="1696538750437" TEXT="Tests erg&#xe4;nzen">
<node COLOR="#338800" CREATED="1696538743210" ID="ID_1370366397" MODIFIED="1697054081393" TEXT="Tests erg&#xe4;nzen">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1696538756712" ID="ID_1688404846" MODIFIED="1697050726434" TEXT="ThreadWrapperAutonomous_test">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1696539571988" ID="ID_791561993" MODIFIED="1696966349054" TEXT="demonstriert: einen Thread komplett abgekoppelt starten">
@ -66322,13 +66323,19 @@
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696538825003" ID="ID_1787030725" MODIFIED="1696539451332" TEXT="ThreadWrapperLifecycle_test">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1696538825003" ID="ID_1787030725" MODIFIED="1697053977126" TEXT="ThreadWrapperLifecycle_test">
<icon BUILTIN="button_ok"/>
<node CREATED="1696538968712" ID="ID_1296694941" MODIFIED="1696538983238" TEXT="sollte das Thema &#xbb;Lifecycle&#xab; insgesamt beleuchten"/>
<node CREATED="1696538984394" ID="ID_1424729205" MODIFIED="1696538993373" TEXT="die neue Variante ist dann nur ein Sonderfall"/>
<node COLOR="#338800" CREATED="1696966333429" ID="ID_232799934" MODIFIED="1696966344964" TEXT="Lifecycle-Hooks in Einsatz zeigen">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1697053980258" ID="ID_907514488" MODIFIED="1697054017174" TEXT="Interaktion mit der Umgebung">
<icon BUILTIN="button_ok"/>
<node CREATED="1697054018477" ID="ID_714874853" MODIFIED="1697054032319" TEXT="das ist eine vereinfachte Demo f&#xfc;r das Setup im Steam-Dispatcher"/>
<node CREATED="1697054033897" ID="ID_265930418" MODIFIED="1697054078497" TEXT="die Komponente erbt vom Thread und bindet Lifecycle-Hooks"/>
<node CREATED="1697054058064" ID="ID_1243838384" MODIFIED="1697054068656" TEXT="und schie&#xdf;t sich selbst am Ende weg"/>
</node>
</node>
</node>
</node>