Library: identified design challenges
On a close look, the wrapper design as pursued here turns out to be prone to insidious data race problems. This was true also for the existing solution, but becomes more clear due to the precise definitions from the C++ standard. This is a confusing situation, because these races typically do not materialise in practice; due to the latency of the OS scheduler the new thread starts invoking user code at least 100µs after the Wrapper object is fully constructed (typically more like 500µs, which is a lot) The standard case (lib::Thread) in its current form is correct, but borderline to undefined behaviour, and any initialisation of members in a derived class would be off limits (the thread-wrapper should not be used as baseclass, rather as member)
This commit is contained in:
parent
88b91d204c
commit
08c3e76f14
3 changed files with 218 additions and 13 deletions
|
|
@ -68,7 +68,7 @@ namespace test{
|
|||
|
||||
namespace {
|
||||
constexpr size_t DEFAULT_RUNS = 10'000'000;
|
||||
constexpr double SCALE = 1e6; // Results are in µ-sec
|
||||
using CLOCK_SCALE = std::micro; // Results are in µ-sec
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -83,12 +83,12 @@ namespace test{
|
|||
benchmarkTime (FUN const& invokeTestLoop, const size_t repeatCnt = DEFAULT_RUNS)
|
||||
{
|
||||
using std::chrono::system_clock;
|
||||
using Dur = std::chrono::duration<double>;
|
||||
using Dur = std::chrono::duration<double, CLOCK_SCALE>;
|
||||
|
||||
auto start = system_clock::now();
|
||||
invokeTestLoop();
|
||||
Dur duration = system_clock::now () - start;
|
||||
return duration.count()/(repeatCnt) * SCALE;
|
||||
return duration.count()/(repeatCnt);
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ namespace test{
|
|||
threadBenchmark(FUN const& subject, const size_t repeatCnt = DEFAULT_RUNS)
|
||||
{
|
||||
using std::chrono::system_clock;
|
||||
using Dur = std::chrono::duration<double>;
|
||||
using Dur = std::chrono::duration<double, CLOCK_SCALE>;
|
||||
|
||||
// the test subject gets the current loop-index and returns a checksum value
|
||||
ASSERT_VALID_SIGNATURE (decltype(subject), size_t(size_t));
|
||||
|
|
@ -194,7 +194,7 @@ namespace test{
|
|||
checksum += thread.checksum;
|
||||
}
|
||||
|
||||
double micros = sumDuration.count() / (nThreads * repeatCnt) * SCALE;
|
||||
double micros = sumDuration.count() / (nThreads * repeatCnt);
|
||||
return std::make_tuple (micros, checksum);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include "lib/iter-explorer.hpp"
|
||||
#include "lib/scoped-collection.hpp"
|
||||
#include "lib/test/microbenchmark.hpp"
|
||||
#include "lib/test/diagnostic-output.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
|
|
@ -40,6 +41,7 @@ using std::atomic_uint;
|
|||
using std::this_thread::yield;
|
||||
using std::this_thread::sleep_for;
|
||||
using std::chrono::microseconds;
|
||||
using std::chrono::system_clock;
|
||||
|
||||
|
||||
namespace lib {
|
||||
|
|
@ -49,6 +51,8 @@ namespace test{
|
|||
|
||||
const uint NUM_THREADS = 200;
|
||||
const uint REPETITIONS = 10;
|
||||
|
||||
using CLOCK_SCALE = std::micro; // Results are in µ-sec
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -73,11 +77,19 @@ namespace test{
|
|||
void
|
||||
defaultWrapperLifecycle()
|
||||
{
|
||||
atomic_uint i{0};
|
||||
Thread thread("counter", [&]{ ++i; }); // bind a λ and launch thread
|
||||
while (thread) yield(); // ensure thread has finished and detached
|
||||
using Dur = std::chrono::duration<double, CLOCK_SCALE>;
|
||||
using Point = system_clock::time_point;
|
||||
Point threadStart;
|
||||
Point afterCtor;
|
||||
Thread thread("lifecycle", [&]{
|
||||
threadStart = system_clock::now();
|
||||
});
|
||||
afterCtor = system_clock::now();
|
||||
while (thread) yield();
|
||||
|
||||
CHECK (i == 1); // verify the effect has taken place
|
||||
double offset = Dur{threadStart - afterCtor}.count();
|
||||
SHOW_EXPR(offset)
|
||||
CHECK (offset > 0);
|
||||
UNIMPLEMENTED ("demonstrate state change");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65253,7 +65253,8 @@
|
|||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1696532676404" ID="ID_91266848" MODIFIED="1696532680013" TEXT="Front-End">
|
||||
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1696532676404" ID="ID_91266848" MODIFIED="1696624166370" TEXT="Front-End">
|
||||
<icon BUILTIN="flag-pink"/>
|
||||
<node CREATED="1696532680835" ID="ID_553798887" MODIFIED="1696536376316">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head/>
|
||||
|
|
@ -65265,7 +65266,7 @@
|
|||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1696532733604" ID="ID_1013220634" MODIFIED="1696532744490" TEXT="und kann ihn nur starten mit einer statischen Factory-Funktion"/>
|
||||
<node CREATED="1696536644665" ID="ID_1557332698" MODIFIED="1696536798494" TEXT="Beschluß: Storage ist inline und an die Lebensdauer gekoppelt">
|
||||
<node COLOR="#5b280f" CREATED="1696536644665" ID="ID_1557332698" MODIFIED="1696624156275" TEXT="Beschluß: Storage ist inline und an die Lebensdauer gekoppelt">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
|
|
@ -65274,9 +65275,10 @@
|
|||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="button_cancel"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1696536837512" ID="ID_1958442975" MODIFIED="1696536975757" TEXT="Lebenszyklus-Zustand wird atomar verwaltet (acquire_release)">
|
||||
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1696536837512" ID="ID_1958442975" MODIFIED="1696624184191" TEXT="Lebenszyklus-Zustand wird atomar verwaltet (acquire_release)">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
|
|
@ -65285,6 +65287,7 @@
|
|||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="flag-pink"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1696532838782" ID="ID_46766207" MODIFIED="1696532858447" TEXT="der Konflikt im Lebensdauer-Thema ist damit beigelegt">
|
||||
|
|
@ -65312,6 +65315,196 @@
|
|||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#ab1d25" CREATED="1696620911198" ID="ID_1727345298" MODIFIED="1696620937599" TEXT="Kritik (nächster Tag)">
|
||||
<icon BUILTIN="stop-sign"/>
|
||||
<node CREATED="1696620943304" ID="ID_211890661" MODIFIED="1696620971910" TEXT="gestern nur in fertigen Komponenten gedacht — das war zu kurz gegriffen">
|
||||
<icon BUILTIN="broken-line"/>
|
||||
<node CREATED="1696620974692" ID="ID_408561702" MODIFIED="1696621055594" TEXT="jede der beiden Varianten impliziert eine abgeleitete Klasse">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
...sonst wäre das ganze Unterfangen theoretischer Natur; nur dadurch wird es interessant, Storage zu verwalten und einen Zugang freizuschalten
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
<node CREATED="1696621067492" ID="ID_1848245258" MODIFIED="1696621085856" TEXT="der Session-Thread braucht einen smart-ptr als Front-End">
|
||||
<node CREATED="1696621086981" ID="ID_427538750" MODIFIED="1696621153920" TEXT="1. es ist ein PImpl (mit eigenem API)"/>
|
||||
<node CREATED="1696621100107" ID="ID_1434548951" MODIFIED="1696621148849" TEXT="2. und die Impl hält den Thread als Member"/>
|
||||
<node CREATED="1696621167042" ID="ID_558727357" MODIFIED="1696621177756" TEXT="3. Zugang wird eigens über Monitor geschützt">
|
||||
<node CREATED="1696621182464" ID="ID_813154978" MODIFIED="1696621189603" TEXT="essentiell für die Logik"/>
|
||||
<node CREATED="1696621190455" ID="ID_613730831" MODIFIED="1696621206993" TEXT="stets nur eine Aktion in Transit"/>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#611224" CREATED="1696621234330" ID="ID_481499226" MODIFIED="1696621321463" TEXT="⟹ in beiden Fällen gelingt es nicht, alles in eine einzige opaque Komponente zu packen"/>
|
||||
</node>
|
||||
<node CREATED="1696621353177" ID="ID_1935311062" MODIFIED="1696621378012" TEXT="was ich aber dennoch unbedingt realisieren möchte: Ordnung der Lifecycle-Klammern">
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696621626749" ID="ID_1402437857" MODIFIED="1696621682930" TEXT="Konsequenz: callback-Hooks im Lifecycle bereitstellen">
|
||||
<icon BUILTIN="yes"/>
|
||||
<node CREATED="1696621694652" ID="ID_489753305" MODIFIED="1696621702239" TEXT="das wirft Probleme auf...">
|
||||
<node CREATED="1696621703203" ID="ID_1346123872" MODIFIED="1696621709128" TEXT="brauche dafür zusätzliche Storage"/>
|
||||
<node CREATED="1696621709770" ID="ID_1531634936" MODIFIED="1696621727446" TEXT="Initialisierung vor Thread-Start">
|
||||
<node CREATED="1696621761419" ID="ID_1692673814" MODIFIED="1696621789787" TEXT="das heißt: lazy builder oder ctor-Args"/>
|
||||
<node CREATED="1696621792391" ID="ID_1048165021" MODIFIED="1696621801098" TEXT="Lesbarkeit ist ebenfalls ein Problem"/>
|
||||
<node CREATED="1696623868041" ID="ID_1255108830" MODIFIED="1696623893825" TEXT="und: wenig Spielraum">
|
||||
<node CREATED="1696623897565" ID="ID_787579556" MODIFIED="1696623905791" TEXT="Thread ist ohnehin schon (zu) komplex"/>
|
||||
<node CREATED="1696623906508" ID="ID_373461507" MODIFIED="1696623932988" TEXT="es gibt eine Variadic-Argumentliste mit perfect-forwarding"/>
|
||||
<node CREATED="1696623940239" ID="ID_1472826799" MODIFIED="1696623960000" TEXT="bisweilen ohnehin schon Typdeduktions-Probleme"/>
|
||||
<node CREATED="1696623960757" ID="ID_400335187" MODIFIED="1696623997084" TEXT="die Typen werden auch noch durch zwei Ebenen druchgegeben und "decayed""/>
|
||||
<node CREATED="1696623997956" ID="ID_801709365" MODIFIED="1696624021191" TEXT="und der Thread-Start steckt in der Basisklasse"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1696624455507" ID="ID_771623143" MODIFIED="1696640533961">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
⟹ <i>Initialisierung</i> von Policy-Bausteinen <i>ist nicht möglich</i>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<arrowlink COLOR="#fe225e" DESTINATION="ID_1852065850" ENDARROW="Default" ENDINCLINATION="147;-294;" ID="Arrow_ID_1803073395" STARTARROW="None" STARTINCLINATION="-332;18;"/>
|
||||
<node CREATED="1696624710262" ID="ID_798050443" MODIFIED="1696624792744" TEXT="Konsequenz: ThreadJoinable ist (latent) racy">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
(seufz)
|
||||
</p>
|
||||
<p>
|
||||
<i>streng genommen</i> könnte die Thread-Funktion bereits mit der Initialisierung des lib::Result beginnen, während ihr dann die default-Initialisierung dazwischenfunkt
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
<node CREATED="1696624920612" ID="ID_1218441049" MODIFIED="1696624940325" TEXT="...und jede Erweiterung, die Daten in der Policy hat, ebenfalls"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696628443151" ID="ID_1852065850" MODIFIED="1696640533961" TEXT="das Race-Problem stellt das ganze Design in Frage">
|
||||
<linktarget COLOR="#fe225e" DESTINATION="ID_1852065850" ENDARROW="Default" ENDINCLINATION="147;-294;" ID="Arrow_ID_1803073395" SOURCE="ID_771623143" STARTARROW="None" STARTINCLINATION="-332;18;"/>
|
||||
<icon BUILTIN="broken-line"/>
|
||||
<node CREATED="1696628561935" ID="ID_183158925" MODIFIED="1696628612622" TEXT="so ziemlich jede Form von Layering und Composition ist betroffen">
|
||||
<icon BUILTIN="closed"/>
|
||||
</node>
|
||||
<node CREATED="1696628634743" ID="ID_860811090" MODIFIED="1696639826869" TEXT="andererseits ist das Problem sehr wahrscheinlich theoretischer Natur">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
da erfahrungsgemäß der OS-Scheduler immer <i>eine gewisse</i> Latenz hat, bis ein Thread tatsächlich ausführbar wird
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<node COLOR="#338800" CREATED="1696628723995" ID="ID_1617045707" MODIFIED="1696639807086" TEXT="nochmal Beobachtungen machen">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1696639643860" ID="ID_82825911" MODIFIED="1696639649431" TEXT="std::system_clock"/>
|
||||
<node CREATED="1696639649973" ID="ID_136142260" MODIFIED="1696639668596" TEXT="Zeitpunkte erfassen"/>
|
||||
<node CREATED="1696639669120" ID="ID_580835805" MODIFIED="1696639673243" TEXT="am Ende vergleichen"/>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1696639673735" ID="ID_125089777" MODIFIED="1696639838019" TEXT="⟹ Thread-Funktion startet stets > 100µs nach dem Ende des ctors">
|
||||
<node CREATED="1696639722881" ID="ID_1727068743" MODIFIED="1696639726364" TEXT="das ist eine Menge"/>
|
||||
<node CREATED="1696639726816" ID="ID_1384372347" MODIFIED="1696639753848" TEXT="(i.d.R sehe ich sogar ≈ 500µs)"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1696639754464" ID="ID_279169592" MODIFIED="1696639859888">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
und trotzdem: das ist <b>kein Gegenbeweis</b>
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="stop-sign"/>
|
||||
</node>
|
||||
<node CREATED="1696639771290" ID="ID_1990815963" MODIFIED="1696639789477" TEXT="...sondern „undefined behaviour in action“"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1696628756484" ID="ID_602079301" MODIFIED="1696629021008">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
man könnte das Thema entschärfen,
|
||||
</p>
|
||||
<p>
|
||||
indem man das Thread-Handle zunächst leer initialisiert
|
||||
</p>
|
||||
<p>
|
||||
und dann erst ganz oben im ctor-chain den Thread startet
|
||||
</p>
|
||||
<p>
|
||||
und per move-assign auf das Handle schiebt.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<icon BUILTIN="idea"/>
|
||||
<node CREATED="1696628821820" ID="ID_1115877978" MODIFIED="1696628844773" TEXT="std::thread is default constructible ⟶ inaktiv"/>
|
||||
<node CREATED="1696628845625" ID="ID_1290073525" MODIFIED="1696628855357" TEXT="und es gibt einen move-assignment-Operator"/>
|
||||
<node CREATED="1696629037023" ID="ID_1091879283" MODIFIED="1696629059464" TEXT="man könnte dann zumindest bei diesem Policy-based-Design bleiben"/>
|
||||
<node CREATED="1696629077442" ID="ID_955648903" MODIFIED="1696629089884" TEXT="und immerhin auch Member in den Policies initialisieren"/>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1696634919834" ID="ID_1703278814" LINK="https://stackoverflow.com/q/77247752/444796" MODIFIED="1696640406805" TEXT="sicherheitshalber auch nochmal auf Stackoverflow gefragt">
|
||||
<icon BUILTIN="info"/>
|
||||
<node COLOR="#338800" CREATED="1696639626943" ID="ID_167373706" MODIFIED="1696640356700" TEXT="mehrere Antworten und alle bestätigen mein Argument">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#e4ddaa" COLOR="#435e98" CREATED="1696639898702" ID="ID_1364351681" MODIFIED="1696640392300" TEXT="einer hat ebenfalls die Lösung mit dem move-assign vorgeschlagen">
|
||||
<icon BUILTIN="ksmiletris"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff00bd" CREATED="1696639942907" ID="ID_773629884" MODIFIED="1696639967679" TEXT="und was nun?">
|
||||
<font NAME="SansSerif" SIZE="13"/>
|
||||
<icon BUILTIN="smiley-angry"/>
|
||||
<node CREATED="1696639992869" ID="ID_237693887" MODIFIED="1696639998160" TEXT="sehe folgende Möglichkeiten">
|
||||
<node CREATED="1696640023536" ID="ID_470066396" MODIFIED="1696640047849" TEXT="„ich hab mich verrant“ ⟹ das Wrapper-Design aufgeben">
|
||||
<node CREATED="1696640075401" ID="ID_1516484124" MODIFIED="1696640085620" TEXT="stets std::thread als direktes Member verwenden"/>
|
||||
<node CREATED="1696640111893" ID="ID_25607343" MODIFIED="1696640134822" TEXT="per anderweitiger Struktur für ausreichende Lebensdauer sorgen"/>
|
||||
<node CREATED="1696640139849" ID="ID_1633568429" MODIFIED="1696640159524" TEXT="Werte in Atomics ablegen und austauschen"/>
|
||||
</node>
|
||||
<node CREATED="1696640180387" ID="ID_1878688951" MODIFIED="1696640198386" TEXT="die Wrapper auf das anfangs definierte Minimum beschränken">
|
||||
<node CREATED="1696640214151" ID="ID_548509988" MODIFIED="1696640256973" TEXT="typischerweise auf Subsystem-Ebene und dort eine member-Var"/>
|
||||
<node CREATED="1696640278102" ID="ID_362520772" MODIFIED="1696640302706" TEXT="Thread (Standardfall) so ändern, daß es im dtor intern ein detach() macht"/>
|
||||
<node CREATED="1696640304051" ID="ID_838020700" MODIFIED="1696640339522" TEXT="dafür fällt detach() auf dem API weg und der SteamDispatcher bleibt wie er war"/>
|
||||
</node>
|
||||
<node CREATED="1696640573023" ID="ID_1848128254" MODIFIED="1696640645673" TEXT="Komplexität akzeptieren und das Baukasten-System erweitern">
|
||||
<node CREATED="1696640688951" ID="ID_308046854" MODIFIED="1696640722766" TEXT="Leistungen">
|
||||
<node CREATED="1696640723816" ID="ID_1672854598" MODIFIED="1696640771200" TEXT="anheben der Abstraktions-Ebene"/>
|
||||
<node CREATED="1696640729546" ID="ID_1634619318" MODIFIED="1696640763097" TEXT="explizit und klar in der Verwendung"/>
|
||||
</node>
|
||||
<node CREATED="1696640782282" ID="ID_808167181" MODIFIED="1696640793141" TEXT="Konfigurations-Schema einführen">
|
||||
<node CREATED="1696640794928" ID="ID_32135588" MODIFIED="1696640811229" TEXT="siehe ElementBoxWidget ⟹ BuilderQualifierSupport"/>
|
||||
<node CREATED="1696641111343" ID="ID_605754599" MODIFIED="1696641133479" TEXT="dieses erweitern um positionale Argumente (Filter)"/>
|
||||
<node CREATED="1696641170367" ID="ID_1556266623" MODIFIED="1696641175588" TEXT="im Konstruktor-Rumpf">
|
||||
<node CREATED="1696641176734" ID="ID_1085483271" MODIFIED="1696641183329" TEXT="zuerst die Konfig auswerten"/>
|
||||
<node CREATED="1696641183773" ID="ID_1987401059" MODIFIED="1696641191081" TEXT="dann explizit den Thread starten"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1696641206002" ID="ID_284758018" MODIFIED="1696641255688" TEXT="Ausblick auf die Zukunft: Priority-Klassen, Supervisor anbinden"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696533317806" ID="ID_641021700" MODIFIED="1696533328356" TEXT="Sicherheit">
|
||||
<icon BUILTIN="yes"/>
|
||||
|
|
@ -81752,7 +81945,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696539035824" ID="ID_1472214443" MODIFIED="1696539109792" TEXT="generische Lösung für dieses Nutz-Muster">
|
||||
<linktarget COLOR="#696992" DESTINATION="ID_1472214443" ENDARROW="Default" ENDINCLINATION="-24;-75;" ID="Arrow_ID_409292846" SOURCE="ID_1071243890" STARTARROW="None" STARTINCLINATION="-43;3;"/>
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<node CREATED="1696539114617" ID="ID_104020495" MODIFIED="1696539443047" TEXT="OptionalThread">
|
||||
<node CREATED="1696539114617" ID="ID_104020495" MODIFIED="1696620523042" TEXT="ThreadHookable">
|
||||
<arrowlink COLOR="#898bab" DESTINATION="ID_1917558827" ENDARROW="Default" ENDINCLINATION="-1027;99;" ID="Arrow_ID_1553665185" STARTARROW="None" STARTINCLINATION="532;77;"/>
|
||||
<icon BUILTIN="info"/>
|
||||
<node CREATED="1696539237403" HGAP="36" ID="ID_922406521" LINK="#ID_137837629" MODIFIED="1696539267790" TEXT="vgl. auch Situation für Gtk-Lumiera" VSHIFT="6">
|
||||
|
|
|
|||
Loading…
Reference in a new issue