diff --git a/src/vault/gear/activity-lang.hpp b/src/vault/gear/activity-lang.hpp index cda2f0bb1..65c251e45 100644 --- a/src/vault/gear/activity-lang.hpp +++ b/src/vault/gear/activity-lang.hpp @@ -116,6 +116,13 @@ namespace gear { } + /** @internal setup a scheduler »Tick« for internal state maintenance */ + Activity& createTick (Time deadline){ return mem_.until(deadline).create (Activity::TICK); } + + /** @internal propagate new clean-up deadline to the BlockFlow allocator */ + void discardBefore (Time deadline) { mem_.discardBefore (deadline); } + + /** * Execution Framework: dispatch performance of a chain of Activities. */ diff --git a/src/vault/gear/load-controller.hpp b/src/vault/gear/load-controller.hpp index 1d86f42d0..8f463b137 100644 --- a/src/vault/gear/load-controller.hpp +++ b/src/vault/gear/load-controller.hpp @@ -151,6 +151,13 @@ namespace gear { TimeVar tendedHead_{Time::ANYTIME}; public: + /** periodic call to build integrated state indicators */ + void + updateState (Time now) + { + /////////////////////////////////////////////////////////////////////////////OOO build integrated load state + } + /** * did we already tend for the indicated next relevant head time? * @note const and non-grooming diff --git a/src/vault/gear/scheduler-invocation.hpp b/src/vault/gear/scheduler-invocation.hpp index db465dbbf..a55035973 100644 --- a/src/vault/gear/scheduler-invocation.hpp +++ b/src/vault/gear/scheduler-invocation.hpp @@ -109,6 +109,14 @@ namespace gear { { } + /** forcibly clear out the schedule */ + void + discardSchedule() + { + instruct_.consume_all([](auto&){/*obliterate*/}); + priority_ = PriorityQueue(); + } + /** * Accept an Activity for time-bound execution */ @@ -183,6 +191,13 @@ namespace gear { and priority_.top().waterlevel <= waterLevel(now); } + /** detect a compulsory Activity with missed deadline */ + bool + isOutOfTime() const + { + return false; /////////////////////////////////////////////////////////////////OOO compulsory Activities + } + bool empty() const { diff --git a/src/vault/gear/scheduler.hpp b/src/vault/gear/scheduler.hpp index 8a3f69889..83a4434f4 100644 --- a/src/vault/gear/scheduler.hpp +++ b/src/vault/gear/scheduler.hpp @@ -130,9 +130,11 @@ namespace gear { namespace { // Scheduler default config - const auto IDLE_WAIT = 20ms; ///< sleep-recheck cycle for workers deemed _idle_ - const size_t DISMISS_CYCLES = 100; ///< number of wait cycles before an idle worker terminates completely - Offset POLL_WAIT_DELAY{FSecs(1,1000)}; ///< delay until re-evaluating a condition previously found unsatisfied + const auto IDLE_WAIT = 20ms; ///< sleep-recheck cycle for workers deemed _idle_ + const size_t DISMISS_CYCLES = 100; ///< number of wait cycles before an idle worker terminates completely + Offset POLL_WAIT_DELAY{FSecs(1,1000)}; ///< delay until re-evaluating a condition previously found unsatisfied + Offset DUTY_CYCLE_PERIOD{FSecs(1,20)}; ///< period of the regular scheduler »tick« for state maintenance. + Offset DUTY_CYCLE_TOLERANCE{FSecs(1,10)}; ///< maximum slip tolerated on duty-cycle start before triggering Scheduler-emergency } @@ -184,22 +186,29 @@ namespace gear { } /** - * + * Spark the engine self-regulation cycle and power up WorkForce */ void ignite() { - UNIMPLEMENTED("suicide"); + TRACE (engine, "Ignite Scheduler Dispatch."); + handleDutyCycle (RealClock::now()); + workForce_.activate(); } /** - * + * Bring down processing destructively as fast as possible. + * Dismiss worker threads as soon as possible, and clear the queues. + * @warning Currently running Activities can not be aborted, but anything + * not yet scheduled will be discarded, irrespective of dependencies */ void terminateProcessing() { - UNIMPLEMENTED("suicide"); + TRACE (engine, "Forcibly terminate Scheduler Dispatch."); + workForce_.awaitShutdown(); + layer1_.discardSchedule(); } @@ -241,12 +250,16 @@ namespace gear { private: + void handleDutyCycle (Time now); + void handleWorkerTermination (bool isFailure) { UNIMPLEMENTED("die harder"); } + void triggerEmergency(); + /** send this thread into a targeted short-time wait. */ activity::Proc scatteredDelay (Time now, LoadController::Capacity); @@ -477,6 +490,55 @@ namespace gear { } + /** + * »Tick-hook« : code to maintain 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. + * Code for all kinds of status updates, low-level clean-up and maintenance work related + * to the building blocks of the scheduler shall be added here. It will be invoked from + * within some (random) worker thread, frequently enough for humans to seem like an + * immediate response, but with sufficient large time period to amortise even slightly + * more computational expensive work; IO and possibly blocking operations should be + * avoided here though. Exceptions emanating from here will shut down the engine. + */ + inline void + Scheduler::handleDutyCycle (Time now) + { + // consolidate queue content + layer1_.feedPrioritisation(); + //////////////////////////////////////////////////////////////////////OOO clean-up of outdated tasks here + if (layer1_.isOutOfTime()) + { + triggerEmergency(); + return; // leave everything as-is + } + + // clean-up of obsolete Activities + activityLang_.discardBefore (now); + + loadControl_.updateState (now); + + if (not empty()) + {// prepare next duty cycle »tick« + Time nextTick = now + DUTY_CYCLE_PERIOD; + Time deadline = nextTick + DUTY_CYCLE_TOLERANCE; + auto& ctx = ExecutionCtx::from (*this); + Activity& tickActivity = activityLang_.createTick (deadline); + ctx.post(nextTick, &tickActivity, ctx); + } + } + + /** + * Trip the emergency brake and unwind processing while retaining all state. + */ + inline void + Scheduler::triggerEmergency() + { + UNIMPLEMENTED ("scheduler overrun -- trigger Emergency"); + } + + }} // namespace vault::gear #endif /*SRC_VAULT_GEAR_SCHEDULER_H_*/ diff --git a/src/vault/gear/work-force.hpp b/src/vault/gear/work-force.hpp index 4c9fc195c..88debd675 100644 --- a/src/vault/gear/work-force.hpp +++ b/src/vault/gear/work-force.hpp @@ -132,9 +132,9 @@ namespace gear { void pullWork() { - ASSERT_VALID_SIGNATURE (decltype(CONF::doWork), SIG_WorkFun); - ASSERT_VALID_SIGNATURE (decltype(CONF::finalHook), SIG_FinalHook); - + ASSERT_MEMBER_FUNCTOR (&CONF::doWork, SIG_WorkFun); + ASSERT_MEMBER_FUNCTOR (&CONF::finalHook, SIG_FinalHook); + bool regularExit{false}; try /* ================ pull work ===================== */ { diff --git a/tests/vault/gear/scheduler-service-test.cpp b/tests/vault/gear/scheduler-service-test.cpp index 40d4d8d7c..20a48d821 100644 --- a/tests/vault/gear/scheduler-service-test.cpp +++ b/tests/vault/gear/scheduler-service-test.cpp @@ -32,7 +32,7 @@ #include "lib/format-cout.hpp" #include "lib/test/microbenchmark.hpp" #include "lib/test/diagnostic-output.hpp"///////////////TODO -//#include "lib/util.hpp" +#include "lib/util.hpp" //#include #include @@ -48,6 +48,7 @@ namespace test { // using lib::time::FrameRate; // using lib::time::Offset; + using util::isnil; using lib::time::Time; using std::this_thread::sleep_for; @@ -75,6 +76,7 @@ namespace test { run (Arg) { simpleUsage(); + verify_StartStop(); invokeWorkFunction(); walkingDeadline(); } @@ -88,7 +90,27 @@ namespace test { { BlockFlowAlloc bFlow; EngineObserver watch; - Scheduler{bFlow, watch}; + Scheduler scheduler{bFlow, watch}; + } + + + + /** @test TODO get the scheduler into running state + * @todo WIP 10/23 ✔ define ⟶ 🔁 implement + */ + void + verify_StartStop() + { + BlockFlowAlloc bFlow; + EngineObserver watch; + Scheduler scheduler{bFlow, watch}; + CHECK (isnil (scheduler)); + + scheduler.ignite(); + CHECK (not isnil (scheduler)); + + scheduler.terminateProcessing(); + CHECK (isnil (scheduler)); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 3382b4687..eef1e7323 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -82227,7 +82227,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -82314,7 +82314,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -82524,6 +82524,28 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + +

+ Habe einige Zeit gezögert, aber nun werden die Verknüpfungen definitiv zu vielfältig. Also stellt der EngineObserver so etwas wie einen »Rück-Kanal« bereit. Leider stellen sich hiermit erneut die gesamten (hinlänglich bekannten) Probleme bezüglich der Domäne eine Benachrichtigungs-Systems. Zu welchem Grad legt das System die Kommunikation fest? Wie offen oder verbindlich sind die Teilnehmer? Wie stark vorherbestimmt sind die Nachrichten-Inhalte? Wieviel Payload ist notwendig? +

+ +
+ + +
+ + + + + + +
@@ -82613,6 +82635,39 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + @@ -82892,8 +82947,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -82981,8 +83036,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -83157,7 +83212,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -83225,9 +83280,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + @@ -83275,16 +83330,98 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + +

+ d.h. was grade eben die Deadline „reißt“, kann auch von niemandem mehr gestartet werden +

+ +
+
+
+
+ + + + + + + + + + + + + +
+
+ + + + + + +
@@ -83296,9 +83433,97 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -83322,7 +83547,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + @@ -83331,9 +83557,42 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + + + + + + + +

+ zwar hätte man aus logischer Sicht gerne, daß alle Informationsfunktionen stets den aktuell gültigen Zustand reflektieren — aber das scheitert an der Natur einer Priority-Queue (welche nicht insgeheim mutable sein kann). Praktisch wirkt sich eine solche Diskrepanz aber fast nicht aus, denn das Ergebnis einer Informationsfunktion kann ohne Besitzt des Grooming-Tokens stets im nächsten Moment schon obsolet sein. Man muß nur aufpassen, daß im Fall einer echten Mutation die Bereinigungs-Funktion vor der Entscheidungslogik aufgerufen wird +

+ +
+ +
+ + + +
+ - + + + + + + + + + + + + +
@@ -88296,15 +88555,19 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + + + - - + + + + @@ -88357,10 +88620,12 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + - + @@ -88369,12 +88634,12 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + @@ -88741,6 +89006,11 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + @@ -92186,6 +92456,202 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + +

+ Für die Implementierung des Schedulers sind Deadlines technisch irrelevant — deshalb habe ich aktuell auch die ganze Implementierung gecodet, und noch keine Deadline-Behandlung eingebaut. Aber sowohl logisch, als auch im Hinblick auf die Allokationen muß jede Activity eine Deadline haben. Eine Activity ohne Deadline wäre ultimativ verbindlich: irgendwann nach ihrem Startzeitpunkt muß sie aktiviert werden, und jede spätere Activity ist durch sie verdeckt +

+ + +
+
+ + + + + + + + + + + + + + + +

+ Problem: wenn Rückstau im Scheduler entsteht, +

+

+ wird jede Deadline irgendwann überfahren +

+ + +
+ + + + + + + + + + + + + + +

+ ...es gibt zwar einen Block-Mechanismus (das Epoch-Gate), doch damit läßt man den Block-Pool anwachsen und die lineare Suche durch die Blöcke macht sich bald bemerkbar +

+ + +
+
+ + + + + + +

+ ...weil dann der Überlauf-Mechanismus zu weit in die Zukunft ausgreift, und sich die Steuerung in absehbarer Zeit nicht mehr fängt. +

+ + +
+
+ + + + + + +

+ Daher ist es nicht möglich, eben sehr große/lange Tasks vorzuplanen; man erzeugt eine Flut leerer Blöcke dazwischen, und die Suchzeiten degenerieren +

+ + +
+
+ + + + + + +

+ Damit scheiden die naheliegenden Reparatur-Ansätze weitgehend aus: es ist nicht möglich, eine „überfahrene“ Activity in die Zukunft zu kopieren, denn wenn die Deadline verstrichen ist, kann sie auch bereits verworfen worden sein, und es ist nicht mehr feststellbar, um was es sich handelte +

+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +

+ der Tick selber ist eine verbindliche Aktivität +

+ + +
+ + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + +

+ sollte automatisch passieren, wenn die internen Gesundheits-Checks scheitern +

+ + +
+
+ + + + + + + + +

+ ...wenn wir noch einen Worker-Pool haben, der mit langlaufenden berechnungen geblockt ist, dann wird jeder reguläre emergency-Shutdown im Destruktor hängen bleiben.Wenn es erst mal so weit gekommen ist, kann man nicht einmal mehr den User richtig benachrichtigen; ein hartes exit()  dagegen würde die bereits geleistete Arbeit einfach verwerfen. +

+ + +
+
+
+ + + + + + + + @@ -92437,6 +92903,73 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + +

+ Der Scheduler arbeitet nur in einem begrenzten Bereich +

+

+ Dieser kann durch alltägliche Umstände verletzt werden +

+

+ Dann ist ein spezielle Notzustand zu aktivieren +

+ + +
+ + +
+ + + + + + + + + + + + + + + + + + + +

+ ...der gesamte Entwurf bisher beruht auf Ereignis-Verknüpfung und sieht keine generische Erfassung aller tatsächlich berechneten Schritte vor. Wenn also die Scheduler-Emergency ausgelöst wird, ist ein zufällig verteiltes Muster an Einzel-Jobs abgeschlossen, und der Rest fehlt. Wollte man hier einen generischen Ansatz für das Wiederanlaufen schaffen, so müßte man alle Berechnungsprozesse unter eine einzige Abstraktion subsummieren können. Das halte ich für gefährlich... +

+ + +
+
+ + + + + + + + + + + + + + + + @@ -92526,6 +93059,34 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + +

+ Beispiel: ein »final Render« bei dem pro Frame mehr als 30 Sekunden Rechenzeit notwendig sind; dies würde die Auslegung des Schedulers sprengen +

+ + +
+
+ + + + + + + + +