From af680cdfd995da6a20f794ca1be028d2c81ccf64 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 26 Dec 2023 15:00:35 +0100 Subject: [PATCH] Scheduler-test: adapt tests to changed logic at entrance - now there can not be any direct dispatch anymore when entering events - thus there is no decision logic at entrance anymore - rather the work-function implementation moved down into Layer-2 - so add a unit-test like coverage there (integration in SchedulerService_test) --- .../vault/gear/scheduler-commutator-test.cpp | 102 +++++++++-- tests/vault/gear/scheduler-service-test.cpp | 8 - tests/vault/gear/scheduler-stress-test.cpp | 2 +- wiki/thinkPad.ichthyo.mm | 165 +++++++++--------- 4 files changed, 168 insertions(+), 109 deletions(-) diff --git a/tests/vault/gear/scheduler-commutator-test.cpp b/tests/vault/gear/scheduler-commutator-test.cpp index dfa0fe968..34a166119 100644 --- a/tests/vault/gear/scheduler-commutator-test.cpp +++ b/tests/vault/gear/scheduler-commutator-test.cpp @@ -100,6 +100,7 @@ namespace test { verify_findWork(); verify_Significance(); verify_postChain(); + verify_dispatch(); integratedWorkCycle(); } @@ -123,7 +124,10 @@ namespace test { // prepare scenario: some activity is enqueued queue.instruct ({activity, when, dead}); -//// sched.postChain (sched.findWork(queue,now), detector.executionCtx,queue);///////////////TODO + // retrieve one event from queue and dispatch it + ActivationEvent act = sched.findWork(queue,now); + ActivityLang::dispatchChain (act, detector.executionCtx); + CHECK (detector.verifyInvocation("CTX-tick").arg(now)); CHECK (queue.empty()); @@ -484,18 +488,15 @@ namespace test { // no one holds the GroomingToken ___ensureGroomingTokenReleased(sched); auto myself = std::this_thread::get_id(); - CHECK (not sched.holdsGroomingToken (myself)); + CHECK (sched.acquireGoomingToken()); - // no effect when empty / no Activity given (usually this can happen due to lock contention) - CHECK (activity::KICK == sched.postChain (ActivationEvent(), queue)); - CHECK (not sched.holdsGroomingToken (myself)); - - // Activity immediately dispatched when on time and GroomingToken can be acquired ///////////////////////////TODO + // Activity with start time way into the past is enqueued, but then discarded CHECK (activity::PASS == sched.postChain (makeEvent(past), queue)); - CHECK (detector.verifyInvocation("testActivity").timeArg(now)); // was invoked immediately - CHECK ( sched.holdsGroomingToken (myself)); - CHECK ( queue.empty()); - detector.incrementSeq(); // Seq-point-1 in the detector log + CHECK (detector.ensureNoInvocation("testActivity")); // not invoked + CHECK (queue.peekHead()); // still in the queue... + CHECK (not sched.findWork (queue,now)); // but it is not retrieved due to deadline + CHECK (not queue.peekHead()); // and thus was dropped + CHECK (queue.empty()); // future Activity is enqueued by short-circuit directly into the PriorityQueue if possible CHECK (activity::PASS == sched.postChain (makeEvent(future), queue)); @@ -520,12 +521,11 @@ namespace test { CHECK (not sched.holdsGroomingToken (myself)); CHECK (not queue.peekHead()); // was enqueued, not executed - // Note: this test achieved one single direct invocation; - // all further cases after Seq-point-1 were queued only - CHECK (detector.ensureNoInvocation("testActivity") - .afterSeqIncrement(1)); + // Note: this test did not cause any direct invocation; + // all provided events were queued only + CHECK (detector.ensureNoInvocation("testActivity")); - // As sanity-check: after the point where we purged the queue, + // As sanity-check: the first event was enqueued and the picked up; // two further cases where enqueued; we could retrieve them if // re-acquiring the GroomingToken and using suitable query-time unblockGroomingToken(); @@ -542,6 +542,71 @@ namespace test { + /** @test verify basic functionality to dequeue and dispatch entries. + * @remark this is actually the core of the [»work-function«](\ref Scheduler::doWork), + * and can not easily be demonstrated on a unit-test level, due to the interplay + * with timing and load distribution. So this test is limited to show _that_ an entry + * passes through the queues and is dispatched + * @see SchedulerService_test::invokeWorkFunction() for a more comprehensive integration test + */ + void + verify_dispatch() + { + // rigged execution environment to detect activations-------------- + ActivityDetector detector; + Activity& activity = detector.buildActivationProbe ("testActivity"); + // set a dummy deadline to pass the sanity check + SchedulerInvocation queue; + SchedulerCommutator sched; + LoadController lCtrl; + + Time start{0,1}; + Time dead{0,10}; + // prepare the queue with one activity + CHECK (Time::NEVER == queue.headTime()); + queue.instruct ({activity, start, dead}); + queue.feedPrioritisation(); + CHECK (start == queue.headTime()); + + // for the first testcase, + // set Grooming-Token to be blocked + blockGroomingToken(sched); + auto myself = std::this_thread::get_id(); + CHECK (not sched.holdsGroomingToken (myself)); + + // invoking the dequeue and dispatch requires some wiring + // with functionality provided by other parts of the scheduler + auto getSchedTime = detector.executionCtx.getSchedTime; + auto executeActivity = [&](ActivationEvent event) + { + return ActivityLang::dispatchChain (event, detector.executionCtx); + }; + + // Invoke the pull-work functionality directly from this thread + // (in real usage, this function is invoked from a worker) + CHECK (activity::KICK == sched.dispatchCapacity (queue + ,lCtrl + ,executeActivity + ,getSchedTime)); + CHECK (not queue.empty()); + // the first invocation was kicked back, + // since the Grooming-token could not be acquired. + unblockGroomingToken(); + + // ...now this thread can acquire, fetch from queue and dispatch.... + CHECK (activity::PASS == sched.dispatchCapacity (queue + ,lCtrl + ,executeActivity + ,getSchedTime)); + + CHECK (queue.empty()); + CHECK (not sched.holdsGroomingToken (myself)); + CHECK (detector.verifyInvocation("testActivity")); + } + + + + /** @test step-wise perform the typical sequence of planning and worker activation * - use the Render-Job scenario from SchedulerActivity_test::scenario_RenderJob() * - use similar instrumentation to trace Activities @@ -585,7 +650,7 @@ namespace test { TimeVar now{Time::ZERO}; // rig the ExecutionCtx to allow manipulating "current scheduler time" - detector.executionCtx.getSchedTime = [&]{ return Time{now}; };///////////////////////////TODO RLY? + detector.executionCtx.getSchedTime = [&]{ return Time{now}; }; // rig the λ-work to verify GroomingToken and to drop it then detector.executionCtx.work.implementedAs( [&](Time, size_t) @@ -611,7 +676,8 @@ namespace test { CHECK (sched.holdsGroomingToken (myself)); // acquired the GroomingToken CHECK (isSameObject(*act, anchor)); // "found" the rigged Activity as next piece of work - sched.postChain (act, queue); ////////////////////////TODO must dispatch here + // dispatch the Activity-chain just retrieved from the queue + ActivityLang::dispatchChain (act, detector.executionCtx); CHECK (queue.empty()); CHECK (not sched.holdsGroomingToken (myself)); // the λ-work was invoked and dropped the GroomingToken diff --git a/tests/vault/gear/scheduler-service-test.cpp b/tests/vault/gear/scheduler-service-test.cpp index e89cfd132..a8792343f 100644 --- a/tests/vault/gear/scheduler-service-test.cpp +++ b/tests/vault/gear/scheduler-service-test.cpp @@ -366,17 +366,9 @@ namespace test { }; - cout << "Scheduled right away..."< - - - +

dies folgt aus der Eigenschaft, daß wir (bisher) eine Abkürzung an der Queue vorbei nehmen, wenn ein Job bereits hinter seiner Startzeit liegt — denn im Zuge der normalen Job-Verarbeitung wird der »Managment-Modus« regulär verlassen, in einem expliziten Prozeß-Schritt. Zusammen bewirkt das eine Unsicherheit, ob auf Ebene des Planungs-Jobs das Grooming-Token gehalten wird, und ob es dort gehalten werden muß. Diese Unsicherheit steht im Widerspruch zum Konzept, welches diesen Belang an die Zugehörigkeit zu Zonen gebunden hat, um auf eine aufwendige, feingranulare Steuerung verzichten zu können. Und die Beobachtungen in den ersten Lasttests scheinen diese Entscheidung zu bestätigen: anscheinend kann das Prüfen und Wechseln des Grooming-Tokens zu Verzögerungen im Bereich 100µs führen (wohl durch stall und Cache-Effekte), sobald das System concurrent läuft. @@ -86072,16 +86070,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

möchte gefühlsmäßig trotzdem daran festhalten

- -
+ @@ -86180,23 +86175,41 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + - - + + + + + + + + + + + + + + +

+ stattdessen zeigen, +

+

+ daß die Deadline wirkt +

+ +
- -
- - + + @@ -86258,7 +86271,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -86270,8 +86283,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -86391,20 +86404,31 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - - - - + + + + + + + + + - - + + - - - + + + + + + + + + + + @@ -86414,12 +86438,12 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - + + + - + @@ -86433,35 +86457,27 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

deshalb treffen hier ein Zustand im Epochen-Pool zusammen mit einem kontextuellen Zustand aus dem Handle, welches aus Performance-Gründen eben die internen Koordinaten aus dem Epochen-Pool verdeckt einbettet

- -
+
- - - +

...der untere Layer kann und darf nichts wissen davon, daß sein Zustand auf einem höheren Layer in eine Abstraktion materialisiert wird. Deshalb wird er im Zuge einer Überlauf-Behandlung seine internen Strukturen reorganisieren und damit das Handle auf dem höheren Layer indirekt invalidieren

- -
+
- - - +

...also nicht nur in dem Fall, an dem ich das Problem entdeckt habe; generell kann das Handle durch jede Allokation invalidiert werden, denn jede Allokation kann eine Kettenreaktion mit Überlauf nach sich ziehen, und dadurch viele Handles entwerten — der Effekt ist nicht lokal @@ -86477,16 +86493,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

es handelt sich nämlich hier um einen hoch problematischen Durchgriff durch die Layer-Struktur des Allokators — und zu allem Überfluß auch noch mitten aus einer anderen Operation heraus als Seiteneffekt, welcher explizit nicht Teil des Iterator-Konzepts ist und daran vorbei auf das Basis-API zugreift. Mit einem Wort, schrecklich.

- -
+
@@ -86507,9 +86520,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

und zwar wenn isWrapped() und idx im oberen Bereich hinter start_ @@ -86518,21 +86529,17 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
Falls die Neu-Allokation nicht so groß ist, verschiebt sich dadurch idx „nur“ in eine Epoche mit früherer Deadline. Ab dem Punkt gehen alle weitere Allokationen in diese frühere Epoche, ohne daß der Aufrufkontext davon etwas mitbekommt.

- -
+
- - - +

...und dann wird die Lage noch schröcklicher: entweder der Extent ist noch uninitialisierte Storage und der Positionszeiger schickt uns dann irgendwohin  und in die allgemeine Speicherkorruption, oder es handelt sich um einen alten, re-cycleten Extent, dessen Werte noch plausibel sind und zu einer funktionierenden Allokation führen, die dann irgendwann später ohne Vorankündigung mit einer neuen Nutzung überschrieben wird.

- -
+
@@ -86540,9 +86547,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

selbst-Gefährdung könnte man reparieren @@ -86551,24 +86556,20 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
 — fremd-Gefährdung nicht

- -
+ - - - +

wenn aber etwas passiert, sind die Folgen destruktiv und nicht zu diagnostizieren

- -
+
@@ -86582,36 +86583,36 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

das Weiterverwenden der Handles ist der größte Hebel, mit dem der Allocator überhaupt in eine akzeptable Perfromance-Zone kommt

- -
+
- - - +

aber einen Check für jeden Zugriff erfordern (teuer)

- -
+
+ + + + + +