Scheduler: solve problem with transport from entrance-queue

The test case "scheduleRenderJob()" -- while deliberately operated
quite artificially with a disabled WorkForce (so the test can check
the contents in the queue and then progress manually -- led to discovery
of an open gap in the logic: in the (rare) case that a new task is
added ''from the outside'' without acquiring the Grooming-Token, then
the new task could sit in the entrace queue, in worst case for 50ms,
until the next Scheduler-»Tick« routinely sweeps this queue. Under
normal conditions however, each dispatch of another activity will
also sweep the entrance queue, yet if there happens to be no other
task right now, a new task could be stuck.

Thinking through this problem also helped to amend some aspects
of Grooming-Token handling and clarified the role of the API-functions.
This commit is contained in:
Fischlurch 2023-11-08 20:58:32 +01:00
parent 7a22e7f987
commit 5c6354882d
6 changed files with 121 additions and 64 deletions

View file

@ -97,6 +97,9 @@ namespace gear {
namespace { // Configuration / Scheduling limit
Offset FUTURE_PLANNING_LIMIT{FSecs{20}}; ///< limit timespan of deadline into the future (~360 MiB max)
/** convenient short-notation, also used by SchedulerService */
auto inline thisThread() { return std::this_thread::get_id(); }
}
@ -115,8 +118,6 @@ namespace gear {
using ThreadID = std::thread::id;
atomic<ThreadID> groomingToken_{};
auto thisThread() { return std::this_thread::get_id(); }
public:
SchedulerCommutator() = default;
@ -195,6 +196,16 @@ namespace gear {
}
/** tend to the input queue if possible */
void
maybeFeed (SchedulerInvocation& layer1)
{
if (layer1.hasPendingInput()
and (holdsGroomingToken(thisThread())
or acquireGoomingToken()))
layer1.feedPrioritisation();
}
/**
* Look into the queues and possibly retrieve work due by now.
* @note transparently discards any outdated entries,

View file

@ -305,6 +305,12 @@ namespace gear {
and isActivated (priority_.top().manifestation));
}
bool
hasPendingInput() const
{
return not instruct_.empty();
}
bool
empty() const
{

View file

@ -306,8 +306,6 @@ namespace gear {
}
void postChain (ActivationEvent); //////////////////////////////////////OOO could be private?
/**
* The worker-Functor: called by the active Workers from the
@ -317,6 +315,7 @@ namespace gear {
private:
void postChain (ActivationEvent);
void handleDutyCycle (Time now);
void handleWorkerTermination (bool isFailure);
void maybeScaleWorkForce();
@ -366,6 +365,13 @@ namespace gear {
return setup;
}
void
ensureDroppedGroomingToken()
{
if (layer2_.holdsGroomingToken (thisThread()))
layer2_.dropGroomingToken();
}
/** access high-resolution-clock, rounded to µ-Ticks */
Time
getSchedTime()
@ -466,6 +472,7 @@ namespace gear {
scheduler_.engineObserver_.dispatchEvent(qualifier, WorkTiming::stop(now));
}
/** λ-tick : scheduler management duty cycle */
activity::Proc
tick (Time now)
{
@ -511,6 +518,7 @@ namespace gear {
, death_
, manID_
, isCompulsory_});
theScheduler_.ensureDroppedGroomingToken();
return move(*this);
}
@ -551,10 +559,10 @@ namespace gear {
inline activity::Proc
Scheduler::getWork()
{
auto self = std::this_thread::get_id();
try {
auto res = WorkerInstruction{}
.performStep([&]{
layer2_.maybeFeed(layer1_);
Time now = getSchedTime();
Time head = layer1_.headTime();
return scatteredDelay(now,
@ -567,6 +575,7 @@ namespace gear {
return layer2_.postDispatch (toDispatch, ctx, layer1_);
})
.performStep([&]{
layer2_.maybeFeed(layer1_);
Time now = getSchedTime();
Time head = layer1_.headTime();
return scatteredDelay(now,
@ -574,15 +583,13 @@ namespace gear {
});
// ensure lock clean-up
if (res != activity::PASS
and layer2_.holdsGroomingToken(self))
layer2_.dropGroomingToken();
if (res != activity::PASS)
ensureDroppedGroomingToken();
return res;
}
catch(...)
{
if (layer2_.holdsGroomingToken (self))
layer2_.dropGroomingToken();
ensureDroppedGroomingToken();
throw;
}
}
@ -606,19 +613,16 @@ namespace gear {
{
auto doTargetedSleep = [&]
{ // ensure not to block the Scheduler after management work
auto self = std::this_thread::get_id();
if (layer2_.holdsGroomingToken (self))
layer2_.dropGroomingToken();
// relocate this thread(capacity) to a time where its more useful
ensureDroppedGroomingToken();
// relocate this thread(capacity) to a time where its more useful
Offset targetedDelay = loadControl_.scatteredDelayTime (now, capacity);
std::this_thread::sleep_for (std::chrono::microseconds (_raw(targetedDelay)));
};
auto doTendNextHead = [&]
{
Time head = layer1_.headTime();
auto self = std::this_thread::get_id();
if (not loadControl_.tendedNext(head)
and (layer2_.holdsGroomingToken(self)
and (layer2_.holdsGroomingToken(thisThread())
or layer2_.acquireGoomingToken()))
loadControl_.tendNext(head);
};

View file

@ -236,7 +236,7 @@ namespace gear {
void
incScale()
{
if (size() > setup_.COMPUTATION_CAPACITY)
if (size() >= setup_.COMPUTATION_CAPACITY)
return;
else
workers_.emplace_back (setup_);

View file

@ -31,6 +31,7 @@
#include "lib/time/timevalue.hpp"
#include "lib/format-cout.hpp"
#include "lib/format-string.hpp"
#include "lib/test/transiently.hpp"
#include "lib/test/microbenchmark.hpp"
#include "lib/test/diagnostic-output.hpp"///////////////TODO
#include "lib/util.hpp"
@ -466,6 +467,9 @@ namespace test {
BlockFlowAlloc bFlow;
EngineObserver watch;
Scheduler scheduler{bFlow, watch};
// prevent scale-up of the Scheuler's WorkForce
TRANSIENTLY(work::Config::COMPUTATION_CAPACITY) = 0;
Time nominal{7,7};
Time start{0,1};
@ -481,8 +485,8 @@ namespace test {
SHOW_EXPR(offset())
auto buidl=
scheduler.defineSchedule(testJob)
.startOffset(200us)
.lifeWindow (1ms);
.startOffset(400us)
.lifeWindow (2ms);
SHOW_EXPR(offset())
buidl .post();
@ -493,7 +497,6 @@ SHOW_EXPR(offset())
sleep_for(400us);
// CHECK (detector.ensureNoInvocation("testJob"));
SHOW_EXPR(offset())
scheduler.layer1_.feedPrioritisation();
auto res= scheduler.getWork();
SHOW_EXPR(offset())
SHOW_EXPR(res)

View file

@ -82730,6 +82730,39 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
</node>
</node>
<node COLOR="#338800" CREATED="1699472012351" FOLDED="true" ID="ID_1226214188" MODIFIED="1699472610417" TEXT="Bedienen der Eingangs-Queue stets sicherstellen">
<linktarget COLOR="#4bd448" DESTINATION="ID_1226214188" ENDARROW="Default" ENDINCLINATION="-543;31;" ID="Arrow_ID_1563551910" SOURCE="ID_1620543415" STARTARROW="None" STARTINCLINATION="-231;0;"/>
<icon BUILTIN="button_ok"/>
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#420d67" CREATED="1699472120064" ID="ID_1534221280" MODIFIED="1699472153348" TEXT="nebbich...">
<font ITALIC="true" NAME="SansSerif" SIZE="12"/>
<icon BUILTIN="smily_bad"/>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1699472162491" ID="ID_903900691" MODIFIED="1699472589008" TEXT="...sonst w&#xfc;rden neue Events schlimmstenfalls bis zum n&#xe4;chsten Tick h&#xe4;ngenbleiben k&#xf6;nnen...">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
<u>Erl&#228;uterung</u>:
</p>
<p>
Die gesamte Entscheidungs-Logik baut auf dem Head der Priority-Queue auf. Sofern man aber das GroomingToken nicht erlangen kann (oder will), gehen neue ActivationEvent erst einmal in die Lock-free Instruct-queue. Sie werden von dort <i>nebenbei mit weiterbef&#246;rdert,</i>&#160;und zwar immer dann, wenn jemand grade das Grooming-Token hat und sich an den Queues zu schaffen macht. Leider ist dieser Umstand nur garantiert der Fall im Scheduler-&#187;Tick&#171; &#8212; wenngleich auch im regul&#228;ren Betrieb fast immer irgendwo was &#8222;unterwegs&#8220; sein sollte, und deshalb auch das <i>nebenbei Auskehren, </i>praktisch immer funktioniert. Aufgefallen war mir das Problem nur in einem k&#252;nstlichen Test-Setup SchedulerService_test::scheduleRenderJob(), wo ich die Worker bewu&#223;t au&#223;er Gefecht gesetzt habe. Aber es gibt eben durchaus m&#246;gliche Umst&#228;nde, wo das auch passieren k&#246;nnte:
</p>
<ul>
<li>
wenn jemand ganz von Au&#223;en etwas einstellt
</li>
<li>
wenn grade ein anderer Worker mit Management-Aufgaben besch&#228;ftigt ist, aber am <i>Auskehren der Eingangsqueue</i>&#160;bereits vorbei ist.
</li>
</ul>
<p>
Grade in letzterem Fall k&#246;nnte es passieren, da&#223; das dummer Weise die letzte Aktion ist, und dann eine lange Pause kommt, bis zum n&#228;chsten Tick. Man k&#246;nnte zwar so etwas leicht beim Einplanen neuer Jobs kompensieren, aber das ist genau die Art von heimt&#252;ckischer Verkoppelung, die ich um jeden Preis zu verhindern suche.
</p>
</body>
</html></richcontent>
<icon BUILTIN="messagebox_warning"/>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1698004014170" ID="ID_359097355" MODIFIED="1699235836029" TEXT="Test">
<icon BUILTIN="pencil"/>
<node COLOR="#435e98" CREATED="1698203252783" ID="ID_161810851" MODIFIED="1698240150155" TEXT="was kann man hier sinnvoll testen?">
@ -83016,8 +83049,8 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
<node CREATED="1699068495938" ID="ID_1790197795" MODIFIED="1699068527242" TEXT="das ist ein internes API &#x2014; aber die zentrale Achse"/>
</node>
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1699313803867" HGAP="-20" ID="ID_1032033223" MODIFIED="1699328444728" TEXT="Logik-Fehler: neu geplanter Job steckt fest" VSHIFT="18">
<linktarget COLOR="#f91d6b" DESTINATION="ID_1032033223" ENDARROW="Default" ENDINCLINATION="-973;63;" ID="Arrow_ID_46385590" SOURCE="ID_550874585" STARTARROW="None" STARTINCLINATION="630;-68;"/>
<node COLOR="#435e98" CREATED="1699313803867" HGAP="-20" ID="ID_1032033223" MODIFIED="1699472735854" TEXT="Logik-Fehler: neu geplanter Job steckt fest" VSHIFT="18">
<linktarget COLOR="#1d60f9" DESTINATION="ID_1032033223" ENDARROW="Default" ENDINCLINATION="-973;63;" ID="Arrow_ID_46385590" SOURCE="ID_550874585" STARTARROW="None" STARTINCLINATION="630;-68;"/>
<icon BUILTIN="broken-line"/>
<node CREATED="1699314171601" ID="ID_911884909" MODIFIED="1699314174654" TEXT="Szenario">
<node CREATED="1699314175722" ID="ID_760916314" MODIFIED="1699314218863" TEXT="Render-Job per high-level API eingestellt">
@ -83028,10 +83061,11 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<icon BUILTIN="forward"/>
</node>
</node>
<node CREATED="1699314283068" ID="ID_1215190474" MODIFIED="1699314330289" TEXT="Problem-Situation">
<node CREATED="1699314283068" ID="ID_1215190474" MODIFIED="1699472726565" TEXT="Problem-Situation">
<icon BUILTIN="info"/>
<node CREATED="1699314331405" ID="ID_601536457" MODIFIED="1699314347447" TEXT="das neue ActivationEvent sitzt noch in der Eingangs-Queue"/>
<node CREATED="1699314348751" ID="ID_786038008" MODIFIED="1699314417293" TEXT="&#x27f9; die pre-Dispatch-Kapazit&#xe4;tssteuerung &#x27fc; idle-WAIT"/>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1699314450362" ID="ID_1548210369" MODIFIED="1699314482186" TEXT="Problem: solange niemand das Grooming-Token hat, passiert kein feedPrioritisation()">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1699314450362" ID="ID_1548210369" MODIFIED="1699472717737" TEXT="Problem: solange niemand das Grooming-Token hat, passiert kein feedPrioritisation()">
<icon BUILTIN="clanbomber"/>
<node CREATED="1699314936837" ID="ID_1589318425" MODIFIED="1699315051503" TEXT="die pre-Dispatch-Kapazit&#xe4;tssteuerung will das (bisher) nicht machen">
<richcontent TYPE="NOTE"><html>
@ -83053,7 +83087,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node CREATED="1699315202665" ID="ID_1173720570" MODIFIED="1699315242168" TEXT="au&#xdf;erdem w&#xfc;rde jeder aktive Dispatch das ausl&#xf6;sen"/>
</node>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1699314501904" ID="ID_39607284" MODIFIED="1699315340563" TEXT="das ist eine L&#xfc;cke im Konzept auf Ebene der Komponenten-Integration">
<node COLOR="#435e98" CREATED="1699314501904" ID="ID_39607284" MODIFIED="1699472722313" TEXT="das ist eine L&#xfc;cke im Konzept auf Ebene der Komponenten-Integration">
<icon BUILTIN="yes"/>
<node CREATED="1699315440090" ID="ID_1962711320" MODIFIED="1699315495053" TEXT="gem&#xe4;&#xdf; geplantem Nutzen nicht wirklich relevant">
<linktarget COLOR="#2b2ab6" DESTINATION="ID_1962711320" ENDARROW="Default" ENDINCLINATION="32;-22;" ID="Arrow_ID_747379729" SOURCE="ID_1445276544" STARTARROW="None" STARTINCLINATION="-224;9;"/>
@ -83075,12 +83109,13 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<arrowlink COLOR="#373a76" DESTINATION="ID_1435123962" ENDARROW="Default" ENDINCLINATION="304;54;" ID="Arrow_ID_928822414" STARTARROW="None" STARTINCLINATION="-255;13;"/>
<icon BUILTIN="button_ok"/>
</node>
<node CREATED="1699401804396" ID="ID_1620543415" MODIFIED="1699402101803" TEXT="tendiere dazu, dieses Problem &#x201e;unter den Teppich zu kehren&#x201c;">
<node COLOR="#338800" CREATED="1699401804396" ID="ID_1620543415" MODIFIED="1699472703173" TEXT="m&#xf6;glichst unauff&#xe4;llig gefixt">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<head/>
<body>
<p>
Ich finde dieses Problem ziemlich &#228;rgerlich, und w&#252;rde es am liebsten &#8222;unter den Teppich kehren&#8220;
</p>
<p>
Warum?
</p>
@ -83099,14 +83134,24 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</li>
</ul>
<p>
Und &#8212; <i>last but not least </i>&#8212; die L&#246;sung ist h&#228;sslich und redundant: man m&#252;&#223;te das in die work-Function ganz am Anfang reinh&#228;ngen. Oder zumindest in die Kapazit&#228;ts-Behandlung (im 1. und 3.Block)
Und &#8212; <i>last but not least </i>&#8212; die L&#246;sung ist h&#228;sslich und redundant; effektiv m&#252;&#223;te der Check in jedem Aufruf der Work-Function laufen, und w&#252;rde auch das Grooming-Token ben&#246;tigen....
</p>
<p>
</p>
<p>
Aber letztlich:&#160;<font color="#8315ca">&#8623;</font>&#160;<b><font color="#c30665">Augen zu und durch </font></b><font color="#8315ca">&#8623; </font>! So eine Falle darf nicht in der Logik bleiben. Der Scheduler ist so komplex, da&#223; ich jetzt Wochen gebraucht habe, um alle Zusammenh&#228;nge herzustellen, und der Code ist extra mit vielen Abstraktionen so geschrieben, da&#223; man immer alles im Detail verstehen mu&#223;. Eine solche L&#252;cke w&#252;rde das untergraben. Daher baue ich jetzt den Fix unauff&#228;lig in die Kapazit&#228;ts-Behandlung mit ein. Und zwar sowohl <b>pre-Dispatch</b>, alsoauch <b>post-Dispatch</b>&#160;&#8212; denn auch bei letzterem h&#228;tten wir noch die gleiche L&#252;cke (wenn jemand pre-Dispatch das Grooming-Token nicht erlangen kann)
</p>
</body>
</html>
</richcontent>
</html></richcontent>
<arrowlink COLOR="#4bd448" DESTINATION="ID_1226214188" ENDARROW="Default" ENDINCLINATION="-543;31;" ID="Arrow_ID_1563551910" STARTARROW="None" STARTINCLINATION="-231;0;"/>
<icon BUILTIN="button_ok"/>
</node>
</node>
</node>
<node COLOR="#338800" CREATED="1699472727759" ID="ID_675352193" MODIFIED="1699472733833" TEXT="Problem gefixt, Test l&#xe4;uft...">
<icon BUILTIN="button_ok"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1697763699242" ID="ID_272943268" MODIFIED="1699414198627" TEXT="EngineObserver-Schnittstelle">
@ -92516,8 +92561,8 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<arrowlink COLOR="#f4336b" DESTINATION="ID_1142273023" ENDARROW="Default" ENDINCLINATION="-737;663;" ID="Arrow_ID_891496003" STARTARROW="None" STARTINCLINATION="1218;-76;"/>
<icon BUILTIN="button_ok"/>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1699243361425" ID="ID_565283839" MODIFIED="1699328504602" TEXT="ruft die work-Function direkt auf">
<icon BUILTIN="flag-pink"/>
<node COLOR="#338800" CREATED="1699243361425" ID="ID_565283839" MODIFIED="1699472780291" TEXT="ruft die work-Function direkt auf">
<icon BUILTIN="button_ok"/>
<node CREATED="1699328505477" ID="ID_1796712383" MODIFIED="1699328750570" TEXT="keine WorkForce">
<icon BUILTIN="yes"/>
</node>
@ -92534,7 +92579,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node CREATED="1699328525667" ID="ID_956033802" MODIFIED="1699328535177" TEXT="WorkForce skaliert automatisch">
<icon BUILTIN="messagebox_warning"/>
</node>
<node CREATED="1699328910407" ID="ID_337063855" MODIFIED="1699328936432" TEXT="Ausweg: WorkForce-St&#xe4;rke manipulieren">
<node COLOR="#435e98" CREATED="1699328910407" FOLDED="true" ID="ID_337063855" MODIFIED="1699472777212" TEXT="Ausweg: WorkForce-St&#xe4;rke manipulieren">
<icon BUILTIN="idea"/>
<node CREATED="1699328956633" ID="ID_1757255005" MODIFIED="1699368367892" TEXT="das w&#xe4;re dann ein WorkForce-Betriebsparameter">
<arrowlink COLOR="#786a97" DESTINATION="ID_1177531499" ENDARROW="Default" ENDINCLINATION="-920;53;" ID="Arrow_ID_1670167461" STARTARROW="None" STARTINCLINATION="61;-138;"/>
@ -92545,12 +92590,15 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1699243391733" ID="ID_1214068544" MODIFIED="1699310666524" TEXT="verifiziert Aufruf per Log">
<icon BUILTIN="broken-line"/>
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1699313612489" ID="ID_550874585" MODIFIED="1699328444728" TEXT="Fehler in der Schedule/Dispatch-Logik">
<arrowlink COLOR="#f91d6b" DESTINATION="ID_1032033223" ENDARROW="Default" ENDINCLINATION="-973;63;" ID="Arrow_ID_46385590" STARTARROW="None" STARTINCLINATION="630;-68;"/>
<node COLOR="#338800" CREATED="1699243391733" ID="ID_1214068544" MODIFIED="1699472763073" TEXT="verifiziert Aufruf per Log">
<icon BUILTIN="button_ok"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1699313612489" ID="ID_550874585" MODIFIED="1699472757847" TEXT="Fehler in der Schedule/Dispatch-Logik">
<arrowlink COLOR="#1d60f9" DESTINATION="ID_1032033223" ENDARROW="Default" ENDINCLINATION="-973;63;" ID="Arrow_ID_46385590" STARTARROW="None" STARTINCLINATION="630;-68;"/>
<icon BUILTIN="broken-line"/>
</node>
<node COLOR="#435e98" CREATED="1699472790287" ID="ID_825157701" MODIFIED="1699472819342" TEXT="Anpassung: ohne WorkForce und Layer2.maybeFeed()">
<icon BUILTIN="idea"/>
</node>
</node>
</node>
</node>
@ -94922,16 +94970,13 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node CREATED="1699403217120" ID="ID_120723001" MODIFIED="1699403224072" TEXT="eine Eingangs-Dispatch-Queue"/>
<node CREATED="1699403254867" ID="ID_1562054545" MODIFIED="1699403320882" TEXT="Einspeisen von Nachrichten sehr effizient">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<head/>
<body>
<p>
Aufruf&#160;&#160;aus der Berechnung heraus m&#246;glich, d.h. darf h&#246;chstens ein paar &#181;s kosten
</p>
</body>
</html>
</richcontent>
</html></richcontent>
</node>
<node CREATED="1699403383634" ID="ID_72666374" MODIFIED="1699403396396" TEXT="Addresse ist codiert in einen size_t - Hash">
<node CREATED="1699404688180" ID="ID_1754251698" MODIFIED="1699404705526" TEXT="sie repr&#xe4;sentiert einen symbolischen, hierarchischen Endpunkt"/>
@ -94944,16 +94989,13 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
<node CREATED="1699405149557" ID="ID_1423036678" MODIFIED="1699405164251">
<richcontent TYPE="NODE"><html>
<head>
</head>
<head/>
<body>
<p>
Empfang/Zustellung laufen <b>single-Threaded</b>
</p>
</body>
</html>
</richcontent>
</html></richcontent>
<node CREATED="1699405165067" ID="ID_229778014" MODIFIED="1699405192201" TEXT="k&#xf6;nnte implementiert sein als ein separater Event-Thread"/>
<node CREATED="1699405193033" ID="ID_1200939317" MODIFIED="1699405209359" TEXT="Alternativ: l&#xe4;uft unter GroomingToken im Scheduler-&#xbb;Tick&#xab;"/>
</node>
@ -94969,16 +95011,13 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
<node CREATED="1699405707070" ID="ID_1718899201" MODIFIED="1699405742710">
<richcontent TYPE="NODE"><html>
<head>
</head>
<head/>
<body>
<p>
<u>Leitbild</u>:&#160;<font face="Monospaced">TimingObservable</font>
</p>
</body>
</html>
</richcontent>
</html></richcontent>
<icon BUILTIN="info"/>
</node>
</node>
@ -94990,16 +95029,13 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<icon BUILTIN="button_ok"/>
<node COLOR="#435e98" CREATED="1699413973971" ID="ID_1307699016" MODIFIED="1699414043369">
<richcontent TYPE="NODE"><html>
<head>
</head>
<head/>
<body>
<p>
<b>EngineEvent</b>-Basisstruktur
</p>
</body>
</html>
</richcontent>
</html></richcontent>
<icon BUILTIN="info"/>
<node CREATED="1699414045739" ID="ID_3506591" MODIFIED="1699414061765" TEXT="Symbol + bin&#xe4;re opaque Payload"/>
<node CREATED="1699414063936" ID="ID_1879697605" MODIFIED="1699414079359" TEXT="f&#xfc;r Payload einen Union-cast-Mechanismus vorsehen"/>
@ -95009,16 +95045,13 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node CREATED="1699414118433" ID="ID_639697248" MODIFIED="1699414141250" TEXT="T ist ein Tupel / Record mit den zu publizierenden Daten"/>
<node CREATED="1699414142638" ID="ID_529883499" MODIFIED="1699414172640">
<richcontent TYPE="NODE"><html>
<head>
</head>
<head/>
<body>
<p>
<u>Gr&#246;&#223;enbeschr&#228;nkung</u>: <b>4 &#187;Slots&#171;</b>
</p>
</body>
</html>
</richcontent>
</html></richcontent>
<icon BUILTIN="yes"/>
</node>
</node>