diff --git a/src/vault/gear/scheduler-commutator.hpp b/src/vault/gear/scheduler-commutator.hpp index 0cf259965..38b46de1a 100644 --- a/src/vault/gear/scheduler-commutator.hpp +++ b/src/vault/gear/scheduler-commutator.hpp @@ -184,6 +184,21 @@ namespace gear { layer1.feedPrioritisation(); } + /** update queue head to discard obsolete content. + * @param now _current time_ to use for decision about dropping tasks + * @return `false` when failing to establish a consistent state due to + * missed **compulsory** entries; should cause **Emergency halt**. + */ + bool + maintainQueueHead (SchedulerInvocation& layer1, Time now) + { + ENSURE (holdsGroomingToken (thisThread())); + layer1.feedPrioritisation(); + while (layer1.isOutdated (now) and not layer1.isOutOfTime(now)) + layer1.pullHead(); + return not layer1.isOutOfTime(now); + } + /** * Look into the queues and possibly retrieve work due by now. * @note transparently discards any outdated entries, @@ -198,17 +213,17 @@ namespace gear { layer1.feedPrioritisation(); while (layer1.isOutdated (now) and not layer1.isOutOfTime(now)) layer1.pullHead(); + if (not maintainQueueHead (layer1,now)) + ALERT (engine, "MISSED compulsory job -- should raise Scheduler-Emergency"); //////////////TICKET #1362 : not clear where Scheduler-Emergency is to be handled and how it can be triggered. See Scheduler::triggerEmergency() + else if (layer1.isDue (now)) - { - if (layer1.isOutOfTime(now)) - UNIMPLEMENTED ("how to trigger a Scheduler-Emergency from here"); ///////////////////////TICKET #1362 : not clear where Scheduler-Emergency is to be handled and how it can be triggered. See Scheduler::triggerEmergency() - else - return layer1.pullHead(); - } } + return layer1.pullHead(); + } return ActivationEvent(); } + /***********************************************************//** * This is the primary entrance point to the Scheduler. * Place the given event into the schedule, with prioritisation diff --git a/src/vault/gear/scheduler-invocation.hpp b/src/vault/gear/scheduler-invocation.hpp index 200a0293d..28228dfad 100644 --- a/src/vault/gear/scheduler-invocation.hpp +++ b/src/vault/gear/scheduler-invocation.hpp @@ -278,10 +278,10 @@ namespace gear { and priority_.top().starting <= waterLevel(now); } - /** determine if Activity at scheduler head missed it's deadline + /** determine if the Activity at scheduler head missed it's deadline. * @warning due to memory management, such an Activity must not be dereferenced */ bool - isMissed (Time now) const + isMissed (Time now) const { return not priority_.empty() and waterLevel(now) > priority_.top().deadline; diff --git a/src/vault/gear/scheduler.hpp b/src/vault/gear/scheduler.hpp index 390f9ef58..e564312aa 100644 --- a/src/vault/gear/scheduler.hpp +++ b/src/vault/gear/scheduler.hpp @@ -71,7 +71,7 @@ ** ** If however a thread is put to work, it will start dequeuing an entry from ** the head of the [priority queue](\ref SchedulerInvocation::pullHead), - ** and start interpreting this entry as a _chain of render activities_ with + ** and start interpreting this entry as a _chain of render activities,_ with ** the help of the [»Activity Language«](\ref ActivityLang::dispatchChain). ** In the typical scenario, after some preparatory checks and notifications, ** the thread [transitions into work mode](\ref Scheduler::ExecutionCtx::work), @@ -94,8 +94,10 @@ ** @see SchedulerCommutator Layer-2 ** @see activity.hpp description of »Render Activities« ** - ** @todo WIP 11/2023 »Playback Vertical Slice« - ** + ** @todo WIP 11/2024 »Playback Vertical Slice« + ** - initial version of Scheduler was built and validated by \ref scheduler-stress-test.cpp + ** - now awaiting integration with Render-Node invocation and Job-Planning + ** - very likely we'll extract a Scheduler-Interface (and this file then becomes a service-impl) */ @@ -112,11 +114,8 @@ #include "vault/gear/load-controller.hpp" #include "vault/gear/engine-observer.hpp" #include "vault/real-clock.hpp" -//#include "lib/symbol.hpp" #include "lib/nocopy.hpp" -//#include "lib/util.hpp" -//#include #include #include @@ -124,8 +123,6 @@ namespace vault{ namespace gear { -// using util::isnil; -// using std::string; using std::move; using lib::time::Time; using lib::time::FSecs; @@ -216,6 +213,9 @@ namespace gear { /******************************************************//** * »Scheduler-Service« : coordinate render activities. * @todo WIP 11/2023 + * - largely completed spring 2024 + * - passes stress testing with good performance + * - **TODO** further integration will require to extract a Scheduler-Interface * @see BlockFlow * @see SchedulerUsage_test */ @@ -515,6 +515,16 @@ namespace gear { }; + + + /***********************************************************************//** + * @remark this is the »internal service entrance point« for render workers + * and is implemented by _combining all parts_ of the Scheduler + * - the processing environment of the ActivityLang + * - Layer-1 for queue management + * - Layer-2 for execution + * - the LoadController + */ inline activity::Proc Scheduler::doWork() { @@ -525,7 +535,7 @@ namespace gear { ExecutionCtx ctx{*this, toDispatch}; return ActivityLang::dispatchChain (toDispatch, ctx); } - ,[this]{ return getSchedTime(); } + ,[this] { return getSchedTime(); } ); } @@ -544,10 +554,10 @@ namespace gear { */ inline ScheduleSpec ScheduleSpec::post() - { // protect allocation -// auto guard = theScheduler_->layer2_.requireGroomingTokenHere();//////////////////////////////////////TODO can we avoid that? + { // execute term-builder on-demand... maybeBuildTerm(); - //set up new schedule by retrieving the Activity-chain... + + // set up new schedule by retrieving the Activity-chain... theScheduler_->postChain ({term_->post(), start_ , death_ , manID_ @@ -607,7 +617,9 @@ namespace gear { } - /** + + + /*****************************************************************//** * Enqueue for time-bound execution, possibly dispatch immediately. * This is the »main entrance« to get some Activity scheduled. * @param actEvent the Activity, start time and deadline @@ -625,7 +637,7 @@ namespace gear { /** - * »Tick-hook« : code to maintain sane running status. + * »Tick-hook« : code to maintain a sane running status. * This function will be invoked [regularly](\ref DUTY_CYCLE_PERIOD) while the scheduler * is actively processing; in fact this function determines when the scheduler falls empty * and can be shut down — and thus regular invocation is equivalent to running state. @@ -645,16 +657,11 @@ namespace gear { { auto guard = layer2_.requireGroomingTokenHere(); - // consolidate queue content - layer1_.feedPrioritisation(); - // clean-up of outdated tasks here - while (layer1_.isOutdated (now) and not layer1_.isOutOfTime(now)) - layer1_.pullHead(); - // protect against missing the deadline of a compulsory task - if (layer1_.isOutOfTime (now)) - { + // consolidate queue and discard outdated tasks + if (not layer2_.maintainQueueHead(layer1_,now)) + { // missed deadline of compulsory task triggerEmergency(); - return; // leave everything as-is + return;// leave everything as-is } // clean-up of obsolete Activities diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index d76b9e982..1c5fa3b2d 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -17022,9 +17022,7 @@ - - - +

...weil ich Stand 8/2018 nicht im Stande bin, @@ -17336,9 +17334,7 @@ - - - +

das Diff wird auf den Platzhalter angewendet @@ -17794,9 +17790,7 @@ - - - +

in diesem Fall ist das dann eine Art Toggle-Button, d.h. er wechselt auch seine Gestalt @@ -18803,9 +18797,7 @@ - - - +

Nein! minimal und natural size sollten gleich sein @@ -19378,9 +19370,7 @@ - - - +

Unterscheidung resize ⟺ rerender @@ -20525,9 +20515,7 @@ - - - +

Standard UI-Mechanik überlassen wir GTK @@ -22863,9 +22851,7 @@ - - - +

...denn die CSS-Node-Namen von Custom-Widgets kann man via GTKmm nicht ändern. @@ -43304,9 +43290,7 @@ - - - +

das hier ist die beste Alternative @@ -44596,9 +44580,7 @@ - - - +

#--◆--# _raw(win.overallSpan().duration()) ? = 307445734561825860 @@ -46361,9 +46343,7 @@ - - - +

für wirklich generische Styles sollte man generische Klassen schaffen @@ -46587,9 +46567,7 @@ - - - +

generisch @@ -46759,9 +46737,7 @@ - - - +

need to bubble up @@ -47054,9 +47030,7 @@ - - - +

ist er aber nicht notwendig, @@ -57521,7 +57495,7 @@ - + @@ -57537,7 +57511,8 @@ - + + @@ -57591,11 +57566,10 @@ Thus I'll marked the problematic location and opened #1362

- -
+
- + @@ -57603,11 +57577,21 @@ ...die definitiv auftreten kann, und die auf einem höheren (derzeit nicht existenten) Level der Architektur behandelt werden muß, als eine Scheduler-Emergency.

- -
+
- + + + + +

+ hab damals einfach »aufgegeben«, +

+

+ da der Trigger-Punkt unpassenderweise in Layer-2 liegt +

+ +
@@ -57615,8 +57599,7 @@ ...bzw dorthin gezogen ist durch den Umbau der Scheduler-Struktur, welche zwar spät erfolgte, als Resultat der Stress-tests, aber insgesamt eine signifikante Verbessung des Codes darstellt. Leider hat dieser Umbau nun dazu geführt, daß Layer-2 diverse »Hooks« auf Service-Level ansprechen muß, und das ist wiederum ein HInweis, daß die Code-Anordnung nicht optimal ist

- -
+ @@ -57634,12 +57617,32 @@ - - + + + + + + + + + + + + + + + +

+ ...und genau diese Entscheidung konnte/wollte ich vor einem halben Jahr nicht treffen (und bin im Moment nicht sicher, ob ich sie jetzt treffen kann) +

+ +
- + +
+ @@ -57647,22 +57650,17 @@ siehe auch Kommentar im Ticket #1362 Scheduler Emergency

- -
- - - -

- https://issues.lumiera.org/ticket/1362#comment:1 -

- - -
+ +
+ + + - + + @@ -57701,12 +57699,11 @@ Hier beobachten wir ein lineares Model der Beladung; die lineare Regression hat einen konstanten Sockel(9ms) knapp oberhalb der bisher tolerierten Overheads für start-up und spin-down (+Test-setup); erwartet werden ~6ms

- -
+
- + @@ -57730,8 +57727,7 @@ wenn der Test durläuft sind die beobachteten Kenndaten des Schedulers wie erwartet

- -
+ @@ -57739,15 +57735,20 @@ also die Concurrency ist wirklich gut, die back-to-back-Zeit weicht nur 10ms vom theoretischen Wert ab

- -
+
- + + + + + + - + + @@ -57764,8 +57765,7 @@ Dieser Test skaliert den Worker-Pool, wartet dann jeweils eine (fest konfigurierte) Zeitspanne, um anschließend den Status zu prüfen — ein bekanntermaßen fragiles Schema, obwohl ich inzwischen Timings ausgeknobelt habe, die auf meiner Maschine hinreichend sicher sind. Für zuverlässiges Testen müßte man auf den Status der WorkForce eigens warten, und dafür möchte ich jedoch kein Core-API bereitstellen, denn ich möchte niemanden ermutigen, in Richtung einer »pinball-machine« zu gehen....

- -
+
@@ -98442,6 +98442,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+
@@ -113286,6 +113287,17 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + +

+ es gibt Situationen, in denen das Schedule »gebrochen« wird, und das führt u.U. zum Deadlock bzw. dazu, daß ein Berechnungvorgang stillschweigend stecken bleibt; diese Situationen können relativ leicht auf einem unteren Level im Code erkannt werden, aber von dort ist es nicht einfach, einen Alarm auf einem globalen Level auszulösen +

+ +
+ +
@@ -125741,7 +125753,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -125765,7 +125777,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -130200,7 +130212,7 @@ std::cout << tmpl.render({"what", "World"}) << s - + @@ -130815,7 +130827,7 @@ std::cout << tmpl.render({"what", "World"}) << s - + @@ -130886,11 +130898,11 @@ std::cout << tmpl.render({"what", "World"}) << s - + - + @@ -130899,13 +130911,31 @@ std::cout << tmpl.render({"what", "World"}) << s

+ +
+ + - + + + + +

+ genauer: eine externe Reaktion ist sicher nicht etws, was Layer-2 einfach machen kann — hier entstehe eine zunehmend komplexe Dependency-Injection, insofern der Layer-2 auch Entscheidungen für den Service als Ganzes trifft +

+ +
+ +
+ + + +
@@ -130917,8 +130947,10 @@ std::cout << tmpl.render({"what", "World"}) << s - + + + @@ -131426,7 +131458,7 @@ std::cout << tmpl.render({"what", "World"}) << s - + @@ -132465,6 +132497,22 @@ std::cout << tmpl.render({"what", "World"}) << s + + + + +

+ ...wenn diese naiv reingelassen werden und die gesamte Work-Kapazität ausschöpfen, führt der nächste »Tick«-Job zur Scheduler-Emergency. +

+ +
+ + + + + + +
@@ -132534,6 +132582,45 @@ std::cout << tmpl.render({"what", "World"}) << s
+ + + + + + + + +

+ wenn man einen Verarbeitungsvorkang darstellt als Jobs mit Dependency-Verkettung, dann kann diese Verarbeitung ohne Weiteres irgendwo stecken bleiben, ohne daß ein Fehler ersichtlich ist; der einzige Signalisierungs-Mechanismus sind compulsory-Jobs (und das ist ein relativ brachialer Mechanismus) +

+ +
+
+ + + + + + + +

+ Es sind Render-jobs denkbar, die mehrere Stunden brauchen! +

+

+ Da es keine übergeordnete Koordinierungs-Instanz gibt, muß in diesem Fall von der Job-Planung aus für frei bleibende Restkapazität für Admin-Aufgaben gesorgt werden — denn wenn alle verfügbaren Worker mit solchen langwierigen Berechnungen belegt sind, findet kein »Tick« mehr statt (und der nächste Tick hat seine Deadline überfahren und löst eine Scheduler-Emergency aus). Ohne eine übergeordnete Kapazitäts- und Engine-Steuerung ist diese Situation nicht adäquat zu handhaben: entweder wir bekommen permanent und erwartbar eine Fehler-Situation, oder wir können die Parallelität nicht ausschöpfen, weil wir eine ganze Core freihalten müssen, oder wir müssen überprovisionieren. +

+ +
+ + +
+
+ + + + + +
@@ -133235,7 +133322,7 @@ std::cout << tmpl.render({"what", "World"}) << s

- +
@@ -133272,6 +133359,24 @@ std::cout << tmpl.render({"what", "World"}) << s
+ + + + + + + +

+ ...wenn diese die gesamte Work-Kapazität ausschöpfen, führt der nächste »Tick«-Job zur Scheduler-Emergency. +

+ +
+ +
+ + + +
@@ -133284,7 +133389,7 @@ std::cout << tmpl.render({"what", "World"}) << s

- +
@@ -133702,6 +133807,17 @@ std::cout << tmpl.render({"what", "World"}) << s + + + + + + + + + + + @@ -133712,12 +133828,15 @@ std::cout << tmpl.render({"what", "World"}) << s - +

- derzeit eingestellt auf 50ms + derzeit eingestellt auf 200ms +

+

+ (das sind Tick-Zyklen — nicht klar ob das reicht, denn in den Streß-Tests habe ich gesehen, daß das Schedule durchaus leicht mal um mehrere 100ms weg-rutschen kann)

@@ -134138,10 +134257,31 @@ std::cout << tmpl.render({"what", "World"}) << s
- + + + + + + + + +

+ Sowohl der Tick-Service, alsauch das pullWork müssen die Queue aktualisieren und obsolete Einträge wegwerfen; dabei kann aber eine Scheduler-Emergency auftreten (konkret: ein compulsory-task steht am Queue-Head, hat aber bereits seine Deadline überfahren, weshalb man ihn nur noch wegwerfen kann, denn der zugehörige Activity-Chain könnte bereits dealloziert bzw. recycled sein; und im Besonderen könnte dieser compulsory-task der nächste Tick sein). +

+

+ Aber Scheduler-Emergency gehört ganz eindeutig auf den top-Level, und muß dann sogar nach außen signalisiert werden, weil der Scheduler dieses Problem nicht selbst lösen kann +

+ +
+
+ + + + +
@@ -134187,6 +134327,16 @@ std::cout << tmpl.render({"what", "World"}) << s + + + + + + + + + +
@@ -134201,8 +134351,8 @@ std::cout << tmpl.render({"what", "World"}) << s
- - + +