diff --git a/src/vault/gear/scheduler-commutator.hpp b/src/vault/gear/scheduler-commutator.hpp index 2acd88029..ea54cb449 100644 --- a/src/vault/gear/scheduler-commutator.hpp +++ b/src/vault/gear/scheduler-commutator.hpp @@ -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 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, diff --git a/src/vault/gear/scheduler-invocation.hpp b/src/vault/gear/scheduler-invocation.hpp index fb9ed20b9..c57f87335 100644 --- a/src/vault/gear/scheduler-invocation.hpp +++ b/src/vault/gear/scheduler-invocation.hpp @@ -305,6 +305,12 @@ namespace gear { and isActivated (priority_.top().manifestation)); } + bool + hasPendingInput() const + { + return not instruct_.empty(); + } + bool empty() const { diff --git a/src/vault/gear/scheduler.hpp b/src/vault/gear/scheduler.hpp index 26b196ab7..e605f91fb 100644 --- a/src/vault/gear/scheduler.hpp +++ b/src/vault/gear/scheduler.hpp @@ -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); }; diff --git a/src/vault/gear/work-force.hpp b/src/vault/gear/work-force.hpp index a9a565f2c..0dbcd495f 100644 --- a/src/vault/gear/work-force.hpp +++ b/src/vault/gear/work-force.hpp @@ -236,7 +236,7 @@ namespace gear { void incScale() { - if (size() > setup_.COMPUTATION_CAPACITY) + if (size() >= setup_.COMPUTATION_CAPACITY) return; else workers_.emplace_back (setup_); diff --git a/tests/vault/gear/scheduler-service-test.cpp b/tests/vault/gear/scheduler-service-test.cpp index 2201020e2..548d357a7 100644 --- a/tests/vault/gear/scheduler-service-test.cpp +++ b/tests/vault/gear/scheduler-service-test.cpp @@ -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) diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index d9dfab276..107b111b2 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -82730,6 +82730,39 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + +

+ Erläuterung: +

+

+ 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 nebenbei mit weiterbefördert, 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-»Tick« — wenngleich auch im regulären Betrieb fast immer irgendwo was „unterwegs“ sein sollte, und deshalb auch das nebenbei Auskehren, praktisch immer funktioniert. Aufgefallen war mir das Problem nur in einem künstlichen Test-Setup SchedulerService_test::scheduleRenderJob(), wo ich die Worker bewußt außer Gefecht gesetzt habe. Aber es gibt eben durchaus mögliche Umstände, wo das auch passieren könnte: +

+
    +
  • + wenn jemand ganz von Außen etwas einstellt +
  • +
  • + wenn grade ein anderer Worker mit Management-Aufgaben beschäftigt ist, aber am Auskehren der Eingangsqueue bereits vorbei ist. +
  • +
+

+ Grade in letzterem Fall könnte es passieren, daß das dummer Weise die letzte Aktion ist, und dann eine lange Pause kommt, bis zum nächsten Tick. Man könnte zwar so etwas leicht beim Einplanen neuer Jobs kompensieren, aber das ist genau die Art von heimtückischer Verkoppelung, die ich um jeden Preis zu verhindern suche. +

+ +
+ +
+
@@ -83016,8 +83049,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -83028,10 +83061,11 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + - + @@ -83053,7 +83087,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -83075,12 +83109,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - - + +

+ Ich finde dieses Problem ziemlich ärgerlich, und würde es am liebsten „unter den Teppich kehren“ +

Warum?

@@ -83099,14 +83134,24 @@ Date:   Thu Apr 20 18:53:17 2023 +0200

- Und — last but not least — die Lösung ist hässlich und redundant: man müßte das in die work-Function ganz am Anfang reinhängen. Oder zumindest in die Kapazitäts-Behandlung (im 1. und 3.Block) + Und — last but not least — die Lösung ist hässlich und redundant; effektiv müßte der Check in jedem Aufruf der Work-Function laufen, und würde auch das Grooming-Token benötigen.... +

+

+ +

+

+ Aber letztlich:  Augen zu und durch ! So eine Falle darf nicht in der Logik bleiben. Der Scheduler ist so komplex, daß ich jetzt Wochen gebraucht habe, um alle Zusammenhänge herzustellen, und der Code ist extra mit vielen Abstraktionen so geschrieben, daß man immer alles im Detail verstehen muß. Eine solche Lücke würde das untergraben. Daher baue ich jetzt den Fix unauffälig in die Kapazitäts-Behandlung mit ein. Und zwar sowohl pre-Dispatch, alsoauch post-Dispatch — denn auch bei letzterem hätten wir noch die gleiche Lücke (wenn jemand pre-Dispatch das Grooming-Token nicht erlangen kann)

- -
+ + +
+ + +
@@ -92516,8 +92561,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -92534,7 +92579,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -92545,12 +92590,15 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - - + + + + + + + @@ -94922,16 +94970,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Aufruf  aus der Berechnung heraus möglich, d.h. darf höchstens ein paar µs kosten

- -
+
@@ -94944,16 +94989,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Empfang/Zustellung laufen single-Threaded

- -
+ @@ -94969,16 +95011,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

LeitbildTimingObservable

- -
+
@@ -94990,16 +95029,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

EngineEvent-Basisstruktur

- -
+ @@ -95009,16 +95045,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Größenbeschränkung: 4 »Slots«

- -
+