diff --git a/src/vault/gear/activity-lang.cpp b/src/vault/gear/activity-lang.cpp index 6924ee4e8..96f966a7d 100644 --- a/src/vault/gear/activity-lang.cpp +++ b/src/vault/gear/activity-lang.cpp @@ -107,8 +107,8 @@ namespace gear { + util::toString(data_.timing.quality); case NOTIFY: return util::showAddr(data_.notification.target) - + ", report=" - + util::toString(data_.notification.report); + + ", timing:" + + util::toString(Time{data_.notification.timing}); case GATE: return "<" + util::toString(data_.condition.rest) diff --git a/src/vault/gear/activity-term.hpp b/src/vault/gear/activity-term.hpp index fdfc50f6f..e472bd7a4 100644 --- a/src/vault/gear/activity-term.hpp +++ b/src/vault/gear/activity-term.hpp @@ -176,7 +176,7 @@ namespace gear { REQUIRE (notificationSrc.is (Activity::NOTIFY)); setupGate(); gate_->incDependencies(); - notificationSrc.setNotificationTarget (gate_); + notificationSrc.setNotificationTarget (gate_, Time{post_->data_.timeWindow.life}); return *this; } diff --git a/src/vault/gear/activity.hpp b/src/vault/gear/activity.hpp index 813284221..169f65b77 100644 --- a/src/vault/gear/activity.hpp +++ b/src/vault/gear/activity.hpp @@ -186,6 +186,8 @@ namespace gear { , Time now , void* executionCtx) =0; + virtual Time getDeadline() const =0; + virtual std::string diagnostic() const { @@ -204,8 +206,6 @@ namespace gear { * The Execution Context need to be passed to any Activity _activation;_ * it provides the _bindings_ for functionality defined only on a conceptual * level, and provided by an opaque implementation (actually the Scheduler) - * @remark `getWaitDelay` was once used for Gate, but is now an obscure - * fall-back for _other notifications_ (retained for future use) */ template constexpr void @@ -216,7 +216,6 @@ namespace gear { ASSERT_MEMBER_FUNCTOR (&EXE::done, void(Time, size_t)); ASSERT_MEMBER_FUNCTOR (&EXE::tick, Proc(Time)); - ASSERT_MEMBER_FUNCTOR (&EXE::getWaitDelay, Offset()); ASSERT_MEMBER_FUNCTOR (&EXE::getSchedTime, Time()); } @@ -291,6 +290,7 @@ namespace gear { bool isDead (Time now) const { return dead <= now;} bool isHold () const { return rest > 0; } bool isFree (Time now) const { return not (isHold() or isDead(now)); } + Time getDeadline() const { return Time{dead}; } void incDependencies() { ++rest; } Time @@ -320,7 +320,7 @@ namespace gear { struct Notification { Activity* target; - size_t report; + Instant timing; }; @@ -367,10 +367,11 @@ namespace gear { } explicit - Activity (Activity* target) noexcept + Activity (Activity* target, Time limitWhen =Time::ANYTIME) noexcept : Activity{NOTIFY} { data_.notification.target = target; + data_.notification.timing = limitWhen; } explicit @@ -424,13 +425,10 @@ namespace gear { template activity::Proc dispatch (Time now, EXE& executionCtx); - template - activity::Proc notify (Time now, EXE& executionCtx); - /* === special case access and manipulation === */ bool - is (Activity::Verb expectedVerb) + is (Activity::Verb expectedVerb) const { return expectedVerb == this->verb_; } @@ -443,10 +441,11 @@ namespace gear { } void - setNotificationTarget (Activity* target) + setNotificationTarget (Activity* target, Time limitStart =Time::ANYTIME) { REQUIRE (is (NOTIFY)); data_.notification.target = target; + data_.notification.timing = limitStart; } Time @@ -532,15 +531,6 @@ namespace gear { return activity::PASS; } - template - activity::Proc - notifyTarget (Time now, EXE& executionCtx) - { - REQUIRE (NOTIFY == verb_); - REQUIRE (data_.notification.target); - return data_.notification.target->notify (now, executionCtx); - } - template activity::Proc checkGate (Time now, EXE&) @@ -554,9 +544,8 @@ namespace gear { return activity::PASS; } - template activity::Proc - receiveGateNotification (Time now, EXE& executionCtx) + receiveGateNotification (Time now) { REQUIRE (GATE == verb_); if (data_.condition.rest > 0) @@ -565,33 +554,39 @@ namespace gear { // maybe the Gate has been opened by this notification? if (data_.condition.isFree(now)) {//yes => activate gated chain but lock redundant invocations - Time dead = data_.condition.lockPermanently(); - return postChain (now,dead, executionCtx); + data_.condition.lockPermanently(); + return activity::PASS; } } - return activity::PASS; - } - - template - activity::Proc - dispatchSelf (Time when, Time dead, EXE& executionCtx) - { - return executionCtx.post (when, dead, this, executionCtx); - } - - template - activity::Proc - dispatchSelfDelayed (Time now, Time dead, EXE& executionCtx) - { - dispatchSelf (now + executionCtx.getWaitDelay(), dead, executionCtx); return activity::SKIP; } template activity::Proc - postChain (Time when, Time dead, EXE& executionCtx) + postSelf (Time now, EXE& executionCtx) { REQUIRE (next); - return executionCtx.post (when, dead, next, executionCtx); + if (is(POST)) + return executionCtx.post (Time{data_.timeWindow.life},Time{data_.timeWindow.dead}, this, executionCtx); + else + return executionCtx.post (now,Time::NEVER, this, executionCtx); + } + + template + activity::Proc + postNotify (Time now, EXE& executionCtx) + { + REQUIRE (is(NOTIFY)); + Activity* target = data_.notification.target; + REQUIRE (target); + REQUIRE (not target->is(HOOK) or target->data_.callback.hook); + Time startHint = target->is(GATE) or + target->is(HOOK)? Time{data_.notification.timing} + : now; + Time deadline = target->is(GATE)? target->data_.condition.getDeadline() + : target->is(HOOK)? target->data_.callback.hook->getDeadline() + : Time::NEVER; + // indirectly forward to Activity::dispatch() + return executionCtx.post (startHint,deadline, target, executionCtx); } template @@ -650,11 +645,11 @@ namespace gear { case WORKSTOP: return signalStop (now, executionCtx); case NOTIFY: - return dispatch (now, executionCtx); //▷ special processing for the Notification + return postNotify (now, executionCtx); + case POST: + return postSelf (now, executionCtx); case GATE: return checkGate (now, executionCtx); - case POST: - return dispatchSelf (Time{data_.timeWindow.life},Time{data_.timeWindow.dead}, executionCtx); case FEED: return activity::PASS; case HOOK: @@ -673,6 +668,11 @@ namespace gear { * by invoking the `post`-λ on the \a executionCtx, or by _activating_ * a `POST`-Activity. Control flow passing here has acquired the `GroomingToken` * and can thus assume single threaded execution until `WORKSTART`. + * + * Notably this entrance is used to implement _gating_ to wait for prerequisites; + * when a notification is passed to a `GATE`-Activity, the embedded counter is + * decremented; after all prerequisites are „checked off“ this way, the + * Activity-chain behind the Gate is activated. * @param now the scheduler-time; assuming that a call through the `post`-λ will * only be actually de-queued when scheduler-time equals the start time * defined in the POST activity and passed through the `post`-λ as parameter. @@ -688,46 +688,18 @@ namespace gear { activity::_verify_usable_as_ExecutionContext(); switch (verb_) { - case NOTIFY: - return notifyTarget (now, executionCtx); case POST: case FEED: // signal just to proceed with next... return activity::PASS; + case GATE: + return receiveGateNotification (now); + case HOOK: + return notifyHook (now, executionCtx); default: return activate (now, executionCtx); } } - /** - * Special operation to receive a message or trigger from some other Activity. - * Notably this is used to implement _gating_ to wait for prerequisites; when - * a notification is passed to a `GATE`-Activity, the embedded counter is - * decremented; after all prerequisites are „checked off“ this way, the - * Activity-chain behind the Gate is activated. - * @note this function is invoked from the context of the source, and - * thus any follow-up actions beyond that scope are re-POSTed, - * after possibly performing the GATE-check. - */ - template - activity::Proc - Activity::notify (Time now, EXE& executionCtx) - { - activity::_verify_usable_as_ExecutionContext(); - - switch (verb_) { - case GATE: - return receiveGateNotification (now, executionCtx); - case HOOK: - return notifyHook (now, executionCtx); - case POST: - case FEED: // ▽▽▽▽▽ implies to use a deadline from the context - return postChain (now,Time::NEVER, executionCtx); - default: - return dispatchSelfDelayed (now,Time::NEVER, executionCtx); - } // Fallback: self-re-dispatch for async execution (-> getWaitDelay()) - } - - }} // namespace vault::gear #endif /*SRC_VAULT_GEAR_ACTIVITY_H_*/ diff --git a/src/vault/gear/scheduler.hpp b/src/vault/gear/scheduler.hpp index f10198005..6d304a254 100644 --- a/src/vault/gear/scheduler.hpp +++ b/src/vault/gear/scheduler.hpp @@ -140,7 +140,6 @@ namespace gear { 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 after notification (obscure feature, retained for future use) 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 } @@ -559,12 +558,6 @@ namespace gear { return activity::PASS; } - Offset - getWaitDelay() - { - return POLL_WAIT_DELAY; - } - /** access high-resolution-clock, rounded to µ-Ticks */ Time getSchedTime() diff --git a/tests/vault/gear/activity-detector-test.cpp b/tests/vault/gear/activity-detector-test.cpp index 4dcd10996..e0588baf0 100644 --- a/tests/vault/gear/activity-detector-test.cpp +++ b/tests/vault/gear/activity-detector-test.cpp @@ -240,8 +240,8 @@ namespace test { CHECK (detector.verifyInvocation(CTX_TICK).arg(t)); detector.incrementSeq(); - ctx.tick.returning(activity::KILL); - CHECK (activity::KILL == ctx.tick(t)); + ctx.tick.returning(activity::KICK); + CHECK (activity::KICK == ctx.tick(t)); CHECK (detector.verifyInvocation(CTX_TICK).timeArg(t)); CHECK (detector.verifyInvocation(CTX_WORK).timeArg(t) @@ -360,21 +360,28 @@ namespace test { void watch_notification() { + Time tt{11,11}; // start time of the NOTIFY + Time ts{22,22}; // start time of the target-chain + Time td{33,33}; // deadline for the target-chain ActivityDetector detector; Activity chain; - Activity gate{1, Time{22,22}}; + Activity gate{1, td}; gate.next = &chain; - Activity notification{&gate}; + Activity notification{&gate, ts}; // note: follow-up start time `ts` injected here CHECK (gate.data_.condition.rest == 1); detector.insertActivationTap (notification.data_.notification.target); - Time tt{11,11}; - notification.dispatch (tt, detector.executionCtx); + notification.activate (tt, detector.executionCtx); // dispatch time `tt` (is actually irrelevant here) + // activating the NOTIFY causes it to POST its target, thereby setting the deadline from the GATE + CHECK (detector.verifyInvocation("CTX-post").arg("22.022","33.033", "tap-GATE", "≺test::CTX≻")); - CHECK (detector.verifyInvocation("tap-GATE").arg("11.011 --notify-↯> Act(GATE") - .beforeInvocation("CTX-post").arg("11.011","22.022", "Act(TICK", "≺test::CTX≻")); + detector.incrementSeq(); + // to see the effect of the instrumentation, we need to mimic the behaviour of λ-post, + // which is to call Activity::dispatch() on the given target + notification.data_.notification.target->dispatch (ts, detector.executionCtx); // note: using `ts` for the follow-up chain + CHECK (detector.verifyInvocation("tap-GATE").seq(1).arg("22.022 --notify-↯> Act(GATE")); CHECK (gate.data_.condition.rest == 0); } diff --git a/tests/vault/gear/activity-detector.hpp b/tests/vault/gear/activity-detector.hpp index 2b3dc59c5..cb896dd5f 100644 --- a/tests/vault/gear/activity-detector.hpp +++ b/tests/vault/gear/activity-detector.hpp @@ -102,6 +102,7 @@ namespace test { using lib::time::FSecs; using lib::time::Offset; using lib::meta::RebindVariadic; + using util::unConst; using util::isnil; using std::forward; using std::move; @@ -119,7 +120,6 @@ namespace test { const string CTX_DONE{"CTX-done"}; const string CTX_TICK{"CTX-tick"}; - Offset POLL_WAIT_DELAY{FSecs(1)}; Time SCHED_TIME_MARKER{555,5}; ///< marker value for "current scheduler time" used in tests } @@ -355,61 +355,80 @@ namespace test { { Logger log_; TimeVar invoked_{Time::ANYTIME}; - + + Activity* + target() + { + return reinterpret_cast (data_.callback.arg); + } + + Activity const* + target() const + { + return unConst(this)->target(); + } + activity::Proc activation ( Activity& thisHook , Time now , void* executionCtx) override - { - REQUIRE (thisHook.is (Activity::HOOK)); - invoked_ = now; - if (data_.callback.arg == 0) - {// no adapted target; just record this activation - log_(util::toString(now) + " ⧐ "); - return activity::PASS; - } - else - {// forward activation to the adapted target Activity - Activity& target = *reinterpret_cast (data_.callback.arg); - auto ctx = *static_cast (executionCtx); - log_(util::toString(now) + " ⧐ " + util::toString (target)); - return target.activate (now, ctx); - } - } + { + REQUIRE (thisHook.is (Activity::HOOK)); + invoked_ = now; + if (not target()) + {// no adapted target; just record this activation + log_(util::toString(now) + " ⧐ "); + return activity::PASS; + } + else + {// forward activation to the adapted target Activity + auto ctx = *static_cast (executionCtx); + log_(util::toString(now) + " ⧐ " + util::toString (*target())); + return target()->activate (now, ctx); + } + } activity::Proc notify ( Activity& thisHook , Time now , void* executionCtx) override - { - REQUIRE (thisHook.is (Activity::HOOK)); - invoked_ = now; - if (data_.callback.arg == 0) - {// no adapted target; just record this notification - log_(util::toString(now) + " --notify-↯• "); - return activity::PASS; - } - else - {// forward notification-dispatch to the adapted target Activity - Activity& target = *reinterpret_cast (data_.callback.arg); - auto ctx = *static_cast (executionCtx); - log_(util::toString(now) + " --notify-↯> " + util::toString (target)); - return target.notify (now, ctx); - } - } + { + REQUIRE (thisHook.is (Activity::HOOK)); + invoked_ = now; + if (not target()) + {// no adapted target; just record this notification + log_(util::toString(now) + " --notify-↯• "); + return activity::PASS; + } + else + {// forward notification-dispatch to the adapted target Activity + auto ctx = *static_cast (executionCtx); + log_(util::toString(now) + " --notify-↯> " + util::toString (*target())); + return target()->dispatch (now, ctx); + } + } - std::string - diagnostic() const override - { - return "Probe("+string{log_}+")"; - } + Time + getDeadline() const override + { + if (target() and target()->is(Activity::GATE)) + return target()->data_.condition.getDeadline(); + else + return Time::NEVER; + } + + std::string + diagnostic() const override + { + return "Probe("+string{log_}+")"; + } public: ActivityProbe (string id, EventLog& masterLog, uint const& invocationSeqNr) : Activity{*this, 0} , log_{id, masterLog, invocationSeqNr} { } - + ActivityProbe (Activity const& subject, string id, EventLog& masterLog, uint const& invocationSeqNr) : Activity{*this, reinterpret_cast (&subject)} , log_{id, masterLog, invocationSeqNr} @@ -428,7 +447,7 @@ namespace test { { if (act and act->verb_ == HOOK) { - ActivityProbe* probe = dynamic_cast (act->data_.callback.hook); + ActivityProbe* probe = dynamic_cast (act->data_.callback.hook); if (probe) return probe->invoked_; } @@ -591,7 +610,6 @@ namespace test { _DiagnosticFun::Type done; _DiagnosticFun::Type tick; - function getWaitDelay = [] { return POLL_WAIT_DELAY; }; function getSchedTime = [this]{ return SCHED_TIME_MARKER;}; FakeExecutionCtx (ActivityDetector& detector) diff --git a/tests/vault/gear/scheduler-activity-test.cpp b/tests/vault/gear/scheduler-activity-test.cpp index dfa04b835..d622dc8d3 100644 --- a/tests/vault/gear/scheduler-activity-test.cpp +++ b/tests/vault/gear/scheduler-activity-test.cpp @@ -60,11 +60,11 @@ namespace test { verifyActivity_Post(); verifyActivity_Invoke(); - verifyActivity_Notify_activate(); - verifyActivity_Notify_dispatch(); + verifyActivity_Notify(); verifyActivity_Gate_pass(); verifyActivity_Gate_dead(); verifyActivity_Gate_block(); + verifyActivity_Gate_notify(); verifyActivity_Gate_opened(); termBuilder(); @@ -157,16 +157,16 @@ namespace test { /** @test behaviour of Activity::NOTIFY when _activated_ * - notification is dispatched as special message to an indicated target Activity - * - when activated, a `NOTIFY`-Activity _dispatches_ into the Activity::notify() - * of the target Activity - * - what happens then depends on the target - * - usually the target is a `GATE` (see below), but here for sake of simplicity, - * a `TICK`-Activity is used; on notification, this will _post_ itself as new task, - * with a delay retrieved from context (here configured to be +1sec), and without - * specifying a deadline (thus a deadline from the context will be used). + * - when activated, a `NOTIFY`-Activity invokes the λ-post _with its target,_ + * - in the actual setup (Scheduler) this leads to [dispatching](\ref Activity::dispatch) + * of said target Activity + * - what happens then depends on the target; usually the target is a `GATE` + * - in first example here, we just use a `TICK`-Activity + * - for a `GATE` there is special treatment to inject the _timing window of the target_ + * into the CTX-post invocation; this is essential to handle long notification-chains properly. */ void - verifyActivity_Notify_activate() + verifyActivity_Notify() { Activity chain; Activity notify{&chain}; @@ -174,27 +174,20 @@ namespace test { ActivityDetector detector; Time tt{111,11}; notify.activate (tt, detector.executionCtx); + CHECK (detector.verifyInvocation("CTX-post").arg("11.111", Time::NEVER, "Act(TICK", "≺test::CTX≻")); - CHECK (detector.verifyInvocation("CTX-post").arg("12.111", Time::NEVER, "Act(TICK", "≺test::CTX≻")); - } - - - - /** @test behaviour of Activity::NOTIFY when activation leads to a _dispatch_ - * - when _posting_ a `NOTIFY`, a dedicated _notification_ function is invoked on the chain - * - what actually happens then depends on the receiver; here we just activate a test-Tap - */ - void - verifyActivity_Notify_dispatch() - { - ActivityDetector detector; - // use a diagnostic Tap as chain to detect passing of notification - Activity notify{detector.buildActivationProbe("notifyTargetActivity")}; + detector.incrementSeq(); + // now we use a `GATE` as target + Time ts{333,33}; + Time td{555,55}; + Activity gate{1,td}; + notify.data_.notification.target = &gate; + notify.data_.notification.timing = ts; // start time hint can be packaged into the notification - Time tt{111,11}; - notify.dispatch (tt, detector.executionCtx); - - CHECK (detector.verifyInvocation("notifyTargetActivity").arg("11.111")); + notify.activate (tt, detector.executionCtx); + CHECK (detector.verifySeqIncrement(1) + .beforeInvocation("CTX-post").arg("33.333", "55.555", "Act(GATE", "≺test::CTX≻")); + // NOTE: △△△ start △△△ deadline } @@ -277,14 +270,39 @@ namespace test { + /** @test behaviour of Activity::GATE: + * - if it is _dispatched_ as new chain, instead of just _activating_ + * it of part of an ongoing chain, the Gate will receive a *notification* + * - this results in _decrementing_ the prerequisite latch in the Gate + * - what happens then depends on current state; in this test case + * the Gate is decremented yet remains closed + */ + void + verifyActivity_Gate_notify() + { + Activity chain; + Activity gate{23}; + gate.next = &chain; + + ActivityDetector detector; + Activity& entrance = detector.buildGateWatcher (gate); + + Time tt{333,33}; + CHECK (activity::SKIP == entrance.dispatch (tt, detector.executionCtx)); + CHECK (22 == gate.data_.condition.rest); // prerequisite-count decremented + + CHECK (detector.verifyInvocation("tap-GATE").arg("33.333 --notify-↯> Act(GATE")); + } + + + /** @test behaviour of Activity::GATE on notification * - Gate configured initially such that it blocks * (without violating deadline) - * - thus a regular activation signals to skip the chain, - * but also re-schedules a further check into the future + * - thus a regular activation signals to skip the chain. * - when receiving a notification, the latch is decremented - * - if this causes the Gate to open, the chain is immediately - * scheduled for activation, but the Gate also locked forever + * - if this causes the Gate to open, the follow-up chain will + * be activated immediately, but the Gate also locked forever * - neither a further activation, nor a further notification * has any effect after this point... */ @@ -303,27 +321,26 @@ namespace test { CHECK (gate.data_.condition.dead == td); ActivityDetector detector; - Activity& wiring = detector.buildGateWatcher (gate); + Activity& entrance = detector.buildGateWatcher (gate); // an attempt to activate blocks (returning SKIP, nothing else happens) - CHECK (activity::SKIP == wiring.activate (tt, detector.executionCtx)); - CHECK (1 == gate.data_.condition.rest); // unchanged (and locked)... + CHECK (activity::SKIP == entrance.activate (tt, detector.executionCtx)); + CHECK (1 == gate.data_.condition.rest); // unchanged (and locked)... CHECK (detector.verifyInvocation("tap-GATE").arg("33.333 ⧐ Act(GATE")); detector.incrementSeq(); // Gate receives a notification from some prerequisite Activity - CHECK (activity::PASS == wiring.notify(tt, detector.executionCtx)); + CHECK (activity::PASS == entrance.dispatch(tt, detector.executionCtx)); CHECK (0 == gate.data_.condition.rest); // condition has been decremented... CHECK (detector.verifyInvocation("tap-GATE").seq(0).arg("33.333 ⧐ Act(GATE") - .beforeInvocation("tap-GATE").seq(1).arg("33.333 --notify-↯> Act(GATE") - .beforeInvocation("CTX-post").seq(1).arg(tt,td, "after-GATE", "≺test::CTX≻")); + .beforeInvocation("tap-GATE").seq(1).arg("33.333 --notify-↯> Act(GATE")); CHECK (gate.data_.condition.dead == Time::MIN); detector.incrementSeq(); Time ttt{444,44}; // when another activation happens later, it is blocked to prevent double activation - CHECK (activity::SKIP == wiring.activate (ttt, detector.executionCtx)); + CHECK (activity::SKIP == entrance.activate (ttt, detector.executionCtx)); CHECK (detector.verifyInvocation("tap-GATE").seq(2).arg("44.444 ⧐ Act(GATE")); CHECK (detector.ensureNoInvocation("CTX-post").seq(2) .afterInvocation("tap-GATE").seq(2)); @@ -331,14 +348,13 @@ namespace test { detector.incrementSeq(); // even a further notification has no effect now.... - wiring.notify(ttt, detector.executionCtx); + CHECK (activity::SKIP == entrance.dispatch (ttt, detector.executionCtx)); // conditionals were not touched: CHECK (gate.data_.condition.dead == Time::MIN); CHECK (gate.data_.condition.rest == 0); // the log shows the further notification (at Seq=3) but no dispatch happens anymore CHECK (detector.verifySeqIncrement(3) .beforeInvocation("tap-GATE").seq(3).arg("44.444 --notify-↯> Act(GATE")); - CHECK (detector.ensureNoInvocation("CTX-post").seq(3).arg(tt, "after-GATE", "≺test::CTX≻")); // cout << detector.showLog()< Act(GATE") // ...notification dispatched towards the Gate - .beforeInvocation("CTX-post").seq(2).arg("1.011","2.022","after-Gate","≺test::CTX≻")); // ...this opened the Gate and posted follow-up chain - CHECK (detector.ensureNoInvocation("after-Gate").seq(2) // verify that activation was not passed out directly - .afterInvocation("CTX-post").seq(2)); - CHECK (detector.ensureNoInvocation("CTX-tick").seq(2) // verify also the λ-tick was not invoked directly - .afterInvocation("CTX-post").seq(2)); + // Notification via instrumented connection to the Gate (activate(NOTIFY) -> λ-post(target) -> notify GATE + CHECK (activity::PASS == ActivityLang::dispatchChain (post.next, detector.executionCtx)); // dispatch a notification (case/seq == 2) + CHECK (0 == gate.data_.condition.rest); // Effect of the notification is to decrement the latch + CHECK (detector.verifyInvocation("Gate") .seq(2).arg("1.011 --notify-↯> Act(GATE") // ...notification dispatched towards the Gate + .beforeInvocation("after-Gate").seq(2).arg("1.011 ⧐ Act(TICK") // ...this opened the Gate, passing activation... + .beforeInvocation("CTX-tick") .seq(2).arg("1.011")); // ...to the chain, finally invoking λ-tick } @@ -536,17 +549,15 @@ namespace test { // rig the λ-post to forward dispatch as expected in real usage detector.executionCtx.post.implementedAs( - [&](Time when, Time, Activity* postedAct, auto& ctx) + [&](Time, Time, Activity* postedAct, auto& ctx) { - if (when == ctx.getSchedTime()) // only for POST to run „right now“ - return ActivityLang::dispatchChain (postedAct, ctx); - else - return activity::PASS; + return ActivityLang::dispatchChain (postedAct, ctx); }); ///// Case-1 : send a notification from prerequisite, but prior to activating primary-chain CHECK (activity::PASS == ActivityLang::dispatchChain (&trigger, detector.executionCtx)); - CHECK (detector.verifyInvocation("trigger") .seq(0).arg("5.555 --notify-↯> Act(GATE") // notification dispatched to the Gate + CHECK (detector.verifyInvocation("CTX-post").seq(0).arg("01.000","10.000","trigger","≺test::CTX≻") // notification is POSTed (with time and deadline from target) + .beforeInvocation("trigger") .seq(0).arg("5.555 --notify-↯> Act(GATE") // notification dispatched to the Gate (note: now using curr-sched-time 5.555) .arg("<2, until 0:00:10.000")); // Note: the Gate-latch expects 2 notifications CHECK (detector.ensureNoInvocation("testJob") // ==> the latch was decremented but no invocation yet .afterInvocation("trigger")); @@ -554,9 +565,9 @@ namespace test { ///// Case-2 : now activate the primary-chain detector.incrementSeq(); CHECK (activity::PASS == ActivityLang::dispatchChain (&anchor, detector.executionCtx)); - CHECK (detector.verifyInvocation("deBlock") .seq(1).arg("5.555 --notify-↯> Act(GATE") // immediately at begin, the internal self-notification is dispatched towards the Gate - .arg("<1, until 0:00:10.000") // Note: at this point, the Gate-latch expects 1 notifications - .beforeInvocation("CTX-post").seq(1).arg("5.555","10.0","after-theGate","≺test::CTX≻") //==> latch was decremented, Gate OPENS, posting the tail-chain with Deadline from Gate + CHECK (detector.verifyInvocation("CTX-post").seq(1).arg("01.000","10.000","deBlock","≺test::CTX≻") // at begin, the internal self-notification is POSTed (with time and deadline this chain) + .beforeInvocation("deBlock") .seq(1).arg("5.555 --notify-↯> Act(GATE") // and this call is immediately dispatched towards the Gate (using curr-sched-time 5.555) + .arg("<1, until 0:00:10.000") // Note: at this point, the Gate-latch expects 1 notifications ==> latch decremented, Gate OPENS .beforeInvocation("after-theGate") .arg("5.555 ⧐ Act(WORKSTART") // ...causing the activation to pass behind the Gate .beforeInvocation("CTX-work").seq(1).arg("5.555","") // ...through WORKSTART .beforeInvocation("testJob") .seq(1).arg("7.007",12345) // ...then invoke the JobFunctor itself (with the nominal Time{7,7}) @@ -568,8 +579,9 @@ namespace test { detector.incrementSeq(); CHECK (activity::PASS == ActivityLang::dispatchChain (&trigger, detector.executionCtx)); // and any further external trigger is likewise blocked: - CHECK (detector.verifyInvocation("trigger") .seq(2).arg("5.555 --notify-↯> Act(GATE") // ... it reaches the Gate - .arg("<0, until -85401592:56:01.825")); // ... but the Gate has been closed permanently (by setting the deadline to Time::MIN) + CHECK (detector.verifyInvocation("CTX-post").seq(2).arg("01.000",Time::NEVER,"trigger","≺test::CTX≻") // notification is POSTed (in the real Scheduler the deadline will block already here) + .beforeInvocation("trigger") .seq(2).arg("5.555 --notify-↯> Act(GATE") // ... but even if it would reach the Gate... + .arg("<0, until -85401592:56:01.825")); // ... the Gate has been closed permanently (by setting the deadline to Time::MIN) CHECK (detector.ensureNoInvocation("testJob") // ==> no further invocation .afterInvocation("trigger").seq(2)); @@ -618,6 +630,13 @@ namespace test { CHECK (notify.next->data_.notification.target == followup.post().next); // was wired to the GATE of the follow-up activity Term CHECK (followup.post().next->is (Activity::GATE)); + // rig the λ-post to forward dispatch as expected in real usage + detector.executionCtx.post.implementedAs( + [&](Time, Time, Activity* postedAct, auto& ctx) + { + return ActivityLang::dispatchChain (postedAct, ctx); + }); + ///// Case-1 : trigger off the async IO job CHECK (activity::PASS == ActivityLang::dispatchChain (&anchor, detector.executionCtx)); @@ -631,7 +650,10 @@ namespace test { detector.incrementSeq(); CHECK (activity::PASS == ActivityLang::dispatchChain (¬ify, detector.executionCtx)); CHECK (detector.verifyInvocation("CTX-done").seq(1).arg("5.555", "") // activation of WORKSTOP via callback - .beforeInvocation("CTX-post").seq(1).arg("5.555","10.0","WORKSTART","≺test::CTX≻"));// the attached chain is activated, POSTing the second Activity + .beforeInvocation("CTX-post").seq(1).arg("01.00","10.00","GATE","≺test::CTX≻") // the notification posts the GATE of the follow-up chain + .beforeInvocation("CTX-work").seq(1).arg("5.555", "") // GATE passes -> activation of the follow-up work commences + .beforeInvocation("calcJob") .seq(1) + .beforeInvocation("CTX-done").seq(1).arg("5.555", "")); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index f3b60cbb0..fd0ec5fb9 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -80833,7 +80833,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -80982,7 +80982,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -81267,9 +81267,138 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + + + + + + + + +

+ vor dem Zugang zum GATE muß das Grooming-Token  erlangt werden +

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

+ zweimaliger λ-post wäre die sauberste Lösung — bedingt aber inakzeptablen Overhead +

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

+ wie gut ist es self-composable? +

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

+ aber der Hook kann auch die Rolle jedes activate/notify übernehmen +

+ + +
+
+
+ + + + + + + + + + + + + + + + + + + @@ -81499,6 +81628,11 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + @@ -85461,7 +85595,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -103270,8 +103404,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + @@ -103423,7 +103558,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -103440,8 +103575,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
die von einem einzigen Worker durchgearbeietet werden

- - +
@@ -103455,10 +103589,193 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
wäre ja so zu erwarten gemäß Topologie...

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

+ weil es stets ein letztes Prerequisite gibt, das dann die ganze Lawine an Berechnungen lostritt; der relevante Knoten ist Node-Nr.34(L20) — und die wiederum hat drei Vorläufer-Ketten der Länge 2, d.h. der finale Trigger liegt auf Level 18 oder gar 17. +

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

+ ~3ms — aber 3·400µs ≈ 1.5ms + 100µs hätte ich erwartet +

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

+ bei genauerer Betrachtung löst sich die Feststellung auf.... +

+

+ ja, irgendwo geht hier Zeit verloren, aber im 2.Lauf ist alles nicht so stark ausgeprägt +

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

+ im 2.Lauf mit genauerer Instrumentierung sind die auffälligen Abweichungen innerhalb der Node-Invocation, nicht im administrativen Teil +

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

+ beim Durchdenken nebenbei aufgefallen: unprotected concurrenct acccess +

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

+ ...weil wir Prioritäten gar nicht auf der Ebene des Schedulers repräsentieren, sondern in den übergreifenden Plan einarbeiten können; der Scheduler muß also nur im Bezug auf diesen Plan priorisieren +

+ + +
+
+ + + + + + +

+ ...denn diese zugrundeliegende Struktur ist durch einen vorgelagerten Plan grundsätzlich nicht aus der Welt zu schaffen, denn es geht dabei um eine Diskrepanz zwischen einer planäßigen Codifizierung der Priorität und einer real (sachbezogen) gegebenen Priorität +

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

+ ...ich hatte dabei ein ungutes Gefühl, weil ich mir nicht erklären konnte, warum um ursprünglichen Code diese doppelte Indirektion drinnen war; hab das dann vom Tisch gewischt mit der Vermutung, es sei aus formalen Gründen eingebaut. Jetzt sehe ich, hier gibt es auch einen Gefahrenübergang bezüglich Concurrency, der mich wahrscheinlich damals bewogen hat „vorsichtshalber“ einen POST zu machen... +

+ + +
+ + +
+ + +