diff --git a/src/vault/gear/scheduler-commutator.hpp b/src/vault/gear/scheduler-commutator.hpp index bab2f7664..9a70cb836 100644 --- a/src/vault/gear/scheduler-commutator.hpp +++ b/src/vault/gear/scheduler-commutator.hpp @@ -189,7 +189,7 @@ namespace gear { if (layer1.isDue (now) and not layer1.isOutOfTime(now)) return layer1.pullHead(); } - return ActivationEvent::nil(); + return ActivationEvent(); } @@ -216,26 +216,21 @@ namespace gear { */ template activity::Proc - postDispatch (Activity* chain, Time when + postDispatch (ActivationEvent event ,EXE& executionCtx ,SchedulerInvocation& layer1 - //////////////////////////////////////////////////////////////////////////////////////////////OOO API / Design problem with "context" and significance-Params - , Time dead =Time::NEVER //////////////////////////////////TODO booom!! - , ManifestationID manID =ManifestationID() - , bool compulsory = false - //////////////////////////////////////////////////////////////////////////////////////////////OOO API / Design problem with "context" and significance-Params ) { - if (!chain) return activity::SKIP; + if (!event) return activity::SKIP; Time now = executionCtx.getSchedTime(); - if (decideDispatchNow (when, now)) - return ActivityLang::dispatchChain (chain, executionCtx); + if (decideDispatchNow (event.startTime(), now)) + return ActivityLang::dispatchChain (event, executionCtx); else if (holdsGroomingToken (thisThread())) - layer1.feedPrioritisation (*chain, when, dead, manID, compulsory); + layer1.feedPrioritisation (move (event)); else - layer1.instruct (*chain, when, dead, manID, compulsory); + layer1.instruct (move (event)); return activity::PASS; } }; diff --git a/src/vault/gear/scheduler-invocation.hpp b/src/vault/gear/scheduler-invocation.hpp index 9c5f8701c..636a07635 100644 --- a/src/vault/gear/scheduler-invocation.hpp +++ b/src/vault/gear/scheduler-invocation.hpp @@ -90,14 +90,32 @@ namespace gear { */ struct ActivationEvent { - Activity* activity{nullptr}; - int64_t starting{0}; - int64_t deadline{0}; + Activity* activity; + int64_t starting; + int64_t deadline; uint32_t manifestation :32; - char :0; bool isCompulsory :1; - ///////////////////////////////////////////////////////////////////////////TICKET #1245 : use direct bit-field initialiser in C++20 + + ActivationEvent() + : activity{nullptr} + , starting{_raw(Time::ANYTIME)} + , deadline{_raw(Time::NEVER)} + , manifestation{0} + , isCompulsory{false} + { } + + ActivationEvent(Activity& act, Time when + , Time dead =Time::NEVER + , ManifestationID manID =ManifestationID() + , bool compulsory =false) + : activity{&act} + , starting{_raw(when)} + , deadline{_raw(dead)} + , manifestation{manID} + , isCompulsory{compulsory} + { } + // default copy operations acceptable /** @internal ordering function for time based scheduling * @note reversed order as required by std::priority_queue @@ -112,7 +130,7 @@ namespace gear { operator bool() const { return bool{activity}; } operator Activity*() const { return activity; } - static ActivationEvent nil() { return {nullptr, _raw(Time::ANYTIME), _raw(Time::NEVER), 0, false}; } + Time startTime() const { return Time{TimeValue{starting}};} }; @@ -157,34 +175,27 @@ namespace gear { /** - * Accept an Activity for time-bound execution + * Accept an ActivationEvent with an Activity for time-bound execution */ void - instruct (Activity& activity, Time when - , Time dead =Time::NEVER - , ManifestationID manID =ManifestationID() - , bool compulsory =false) + instruct (ActivationEvent actEvent) { - bool success = instruct_.push (ActivationEvent{&activity - , waterLevel(when) - , waterLevel(dead) - , uint32_t(manID) - , compulsory}); + bool success = instruct_.push (move (actEvent)); if (not success) throw error::Fatal{"Scheduler entrance: memory allocation failed"}; } /** - * Pick up all new Activities from the entrance queue + * Pick up all new events from the entrance queue * and enqueue them to be retrieved ordered by start time. */ void feedPrioritisation() { - ActivationEvent actOrder; - while (instruct_.pop (actOrder)) - priority_.push (move (actOrder)); + ActivationEvent actEvent; + while (instruct_.pop (actEvent)) + priority_.push (move (actEvent)); } @@ -194,16 +205,9 @@ namespace gear { * @remark Layer-2 uses this shortcut when in »grooming mode«. */ void - feedPrioritisation (Activity& activity, Time when - , Time dead =Time::NEVER - , ManifestationID manID =ManifestationID() - , bool compulsory =false) + feedPrioritisation (ActivationEvent actEvent) { - priority_.push (ActivationEvent{&activity - , waterLevel(when) - , waterLevel(dead) - , uint32_t(manID) - , compulsory}); + priority_.push (move (actEvent)); } @@ -214,7 +218,7 @@ namespace gear { ActivationEvent peekHead() { - return priority_.empty()? ActivationEvent::nil() + return priority_.empty()? ActivationEvent() : priority_.top(); } diff --git a/src/vault/gear/scheduler.hpp b/src/vault/gear/scheduler.hpp index 6b3901eca..ba0b1ea08 100644 --- a/src/vault/gear/scheduler.hpp +++ b/src/vault/gear/scheduler.hpp @@ -254,11 +254,8 @@ namespace gear { UNIMPLEMENTED("wrap the ActivityTerm"); } - //////////////////////////////////////////////////////////////////////////////////////////////OOO the role of this function remains unclear; currently used from »Tick« - activity::Proc postChain (Activity*, Time start - , Time dead =Time::ANYTIME - , ManifestationID manID =ManifestationID() - , bool isCompulsory = false); + + void postChain (ActivationEvent); //////////////////////////////////////OOO could be private? /** @@ -317,9 +314,16 @@ namespace gear { return setup; } + /** access high-resolution-clock, rounded to µ-Ticks */ + Time + getSchedTime() + { + return RealClock::now(); + } + /** @internal expose a binding for Activity execution */ class ExecutionCtx; - + friend class ExecutionCtx; /** open private backdoor for tests */ friend class test::SchedulerService_test; @@ -337,26 +341,37 @@ namespace gear { * is in fact supplied by the implementation internals of the scheduler itself. */ class Scheduler::ExecutionCtx - : private Scheduler + : util::NonCopyable { + Scheduler& scheduler_; public: - static ExecutionCtx& - from (Scheduler& self) - { - return static_cast (self); - } + + ActivationEvent rootEvent; + + ExecutionCtx(Scheduler& self, ActivationEvent toDispatch) + : scheduler_{self} + , rootEvent{toDispatch} + { } + /* ==== Implementation of the Concept ExecutionCtx ==== */ /** * λ-post: enqueue for time-bound execution, possibly dispatch immediately. - * This is the »main entrance« to get some Activity scheduled. - * @remark the \a ctx argument is redundant (helpful for test/mocking) + * @remark This function represents and _abstracted entrance to scheduling_ + * for the ActivityLang and is relevant for recursive forwarding + * of activations and notifications. The concrete implementation + * needs some further contextual information, which is passed + * down here as a nested sub-context. */ activity::Proc post (Time when, Activity* chain, ExecutionCtx& ctx) { - return layer2_.postDispatch (chain, when, ctx, layer1_); + ActivationEvent chainEvent = ctx.rootEvent; + chainEvent.activity = chain; + chainEvent.starting = _raw(when); + ExecutionCtx subCtx{scheduler_, chainEvent}; + return scheduler_.layer2_.postDispatch (chainEvent, subCtx, scheduler_.layer1_); } void @@ -374,7 +389,7 @@ namespace gear { activity::Proc tick (Time now) { - handleDutyCycle (now); + scheduler_.handleDutyCycle (now); return activity::PASS; } @@ -388,13 +403,28 @@ namespace gear { Time getSchedTime() { - return RealClock::now(); + return scheduler_.getSchedTime(); } }; + /** + * 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 + * and optionally further context information + */ + inline void + Scheduler::postChain (ActivationEvent actEvent) + { + ExecutionCtx ctx{*this, actEvent}; + layer2_.postDispatch (actEvent, ctx, layer1_); + } + + + /** * @remarks this function is invoked from within the worker thread(s) and will * - decide if and how the capacity of this worker shall be used right now @@ -416,22 +446,22 @@ namespace gear { Scheduler::getWork() { auto self = std::this_thread::get_id(); - auto& ctx = ExecutionCtx::from (*this); try { auto res = WorkerInstruction{} .performStep([&]{ - Time now = ctx.getSchedTime(); + Time now = getSchedTime(); Time head = layer1_.headTime(); return scatteredDelay(now, loadControl_.markIncomingCapacity (head,now)); }) .performStep([&]{ - Time now = ctx.getSchedTime(); - Activity* act = layer2_.findWork (layer1_,now); - return ctx.post (now, act, ctx); + Time now = getSchedTime(); + auto toDispatch = layer2_.findWork (layer1_,now); + ExecutionCtx ctx{*this, toDispatch}; + return layer2_.postDispatch (toDispatch, ctx, layer1_); }) .performStep([&]{ - Time now = ctx.getSchedTime(); + Time now = getSchedTime(); Time head = layer1_.headTime(); return scatteredDelay(now, loadControl_.markOutgoingCapacity (head,now)); @@ -506,16 +536,6 @@ namespace gear { } - //////////////////////////////////////////////////////////////////////////////////////////////OOO the role of this function remains unclear; currently used from »Tick« - inline activity::Proc - Scheduler::postChain (Activity* chain, Time start, Time dead - ,ManifestationID manID, bool isCompulsory) - { - auto& ctx = ExecutionCtx::from (*this); - return layer2_.postDispatch (chain, start, ctx, layer1_ - //////////////////////////////////////////////////////////////////////////////////////////////OOO API / Design problem with "context" and significance-Params - ,dead,manID,isCompulsory); - } /** @@ -555,7 +575,7 @@ namespace gear { Time nextTick = now + DUTY_CYCLE_PERIOD; Time deadline = nextTick + DUTY_CYCLE_TOLERANCE; Activity& tickActivity = activityLang_.createTick (deadline); - postChain (&tickActivity, nextTick, deadline, ManifestationID(), true); + postChain (ActivationEvent{tickActivity, nextTick, deadline, ManifestationID(), true}); } } diff --git a/tests/vault/gear/scheduler-commutator-test.cpp b/tests/vault/gear/scheduler-commutator-test.cpp index 52d950ae1..160056691 100644 --- a/tests/vault/gear/scheduler-commutator-test.cpp +++ b/tests/vault/gear/scheduler-commutator-test.cpp @@ -119,9 +119,9 @@ namespace test { Time now = detector.executionCtx.getSchedTime(); // prepare scenario: some activity is enqueued - queue.instruct (activity, when); + queue.instruct ({activity, when}); - sched.postDispatch (sched.findWork(queue,now), now, detector.executionCtx,queue); + sched.postDispatch (sched.findWork(queue,now), detector.executionCtx,queue); CHECK (detector.verifyInvocation("CTX-tick").arg(now)); CHECK (queue.empty()); @@ -311,15 +311,15 @@ namespace test { Activity a2{2u,2u}; Activity a3{3u,3u}; - queue.instruct (a3, t3); // activity scheduled into the future + queue.instruct ({a3, t3}); // activity scheduled into the future CHECK (not sched.findWork (queue, now)); // ... not found with time `now` CHECK (t3 == queue.headTime()); - queue.instruct (a1, t1); + queue.instruct ({a1, t1}); CHECK (isSameObject (a1, *sched.findWork(queue, now))); // but past activity is found CHECK (not sched.findWork (queue, now)); // activity was retrieved - queue.instruct (a2, t2); + queue.instruct ({a2, t2}); CHECK (isSameObject (a2, *sched.findWork(queue, now))); // activity scheduled for `now` is found CHECK (not sched.findWork (queue, now)); // nothing more found for `now` CHECK (t3 == queue.headTime()); @@ -329,15 +329,15 @@ namespace test { CHECK (not sched.findWork (queue, t3)); CHECK ( queue.empty()); // Everything retrieved and queue really empty - queue.instruct (a2, t2); - queue.instruct (a1, t1); + queue.instruct ({a2, t2}); + queue.instruct ({a1, t1}); CHECK (isSameObject (a1, *sched.findWork(queue, now))); // the earlier activity is found first CHECK (t2 == queue.headTime()); CHECK (isSameObject (a2, *sched.findWork(queue, now))); CHECK (not sched.findWork (queue, now)); CHECK ( queue.empty()); - queue.instruct (a2, t2); // prepare activity which /would/ be found... + queue.instruct ({a2, t2}); // prepare activity which /would/ be found... blockGroomingToken(sched); // but prevent this thread from acquiring the GroomingToken CHECK (not sched.findWork (queue, now)); // thus search aborts immediately CHECK (not queue.empty()); @@ -369,10 +369,10 @@ namespace test { Time t3{30,0}; Activity a3{3u,3u}; Time t4{40,0}; Activity a4{4u,4u}; - queue.instruct (a1, t1, t4, ManifestationID{5}); - queue.instruct (a2, t2, t2); - queue.instruct (a3, t3, t3, ManifestationID{23}, true); - queue.instruct (a4, t4, t4); + queue.instruct ({a1, t1, t4, ManifestationID{5}}); + queue.instruct ({a2, t2, t2}); + queue.instruct ({a3, t3, t3, ManifestationID{23}, true}); + queue.instruct ({a4, t4, t4}); queue.activate(ManifestationID{5}); queue.activate(ManifestationID{23}); @@ -451,19 +451,19 @@ namespace test { auto myself = std::this_thread::get_id(); CHECK (not sched.holdsGroomingToken (myself)); - // no effect when no Activity given - CHECK (activity::SKIP == sched.postDispatch (nullptr, now, detector.executionCtx, queue)); + // no effect when empty / no Activity given + CHECK (activity::SKIP == sched.postDispatch (ActivationEvent(), detector.executionCtx, queue)); CHECK (not sched.holdsGroomingToken (myself)); // Activity immediately dispatched when on time and GroomingToken can be acquired - CHECK (activity::PASS == sched.postDispatch (&activity, past, detector.executionCtx, queue)); + CHECK (activity::PASS == sched.postDispatch (ActivationEvent{activity, past}, detector.executionCtx, 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 // future Activity is enqueued by short-circuit directly into the PriorityQueue if possible - CHECK (activity::PASS == sched.postDispatch (&activity, future, detector.executionCtx, queue)); + CHECK (activity::PASS == sched.postDispatch (ActivationEvent{activity, future}, detector.executionCtx, queue)); CHECK ( sched.holdsGroomingToken (myself)); CHECK (not queue.empty()); CHECK (isSameObject (activity, *queue.peekHead())); // appears at Head, implying it's in Priority-Queue @@ -474,14 +474,14 @@ namespace test { CHECK (queue.empty()); // ...but GroomingToken is not acquired explicitly; Activity is just placed into the Instruct-Queue - CHECK (activity::PASS == sched.postDispatch (&activity, future, detector.executionCtx, queue)); + CHECK (activity::PASS == sched.postDispatch (ActivationEvent{activity, future}, detector.executionCtx, queue)); CHECK (not sched.holdsGroomingToken (myself)); CHECK (not queue.peekHead()); // not appearing at Head this time, CHECK (not queue.empty()); // rather waiting in the Instruct-Queue blockGroomingToken(sched); - CHECK (activity::PASS == sched.postDispatch (&activity, now, detector.executionCtx, queue)); + CHECK (activity::PASS == sched.postDispatch (ActivationEvent{activity, now}, detector.executionCtx, queue)); CHECK (not sched.holdsGroomingToken (myself)); CHECK (not queue.peekHead()); // was enqueued, not executed @@ -562,7 +562,7 @@ namespace test { // ·=================================================================== actual test sequence // Add the Activity-Term to be scheduled for planned start-Time - sched.postDispatch (&anchor, start, detector.executionCtx, queue); + sched.postDispatch (ActivationEvent{anchor, start}, detector.executionCtx, queue); CHECK (detector.ensureNoInvocation("testJob")); CHECK (not sched.holdsGroomingToken (myself)); CHECK (not queue.empty()); @@ -572,11 +572,11 @@ namespace test { detector.incrementSeq(); // Assuming a worker runs "later" and retrieves work... - Activity* act = sched.findWork(queue,now); + ActivationEvent act = sched.findWork(queue,now); CHECK (sched.holdsGroomingToken (myself)); // acquired the GroomingToken CHECK (isSameObject(*act, anchor)); // "found" the rigged Activity as next piece of work - sched.postDispatch (act, now, detector.executionCtx, queue); + sched.postDispatch (act, detector.executionCtx, queue); CHECK (queue.empty()); CHECK (not sched.holdsGroomingToken (myself)); // the λ-work was invoked and dropped the GroomingToken diff --git a/tests/vault/gear/scheduler-invocation-test.cpp b/tests/vault/gear/scheduler-invocation-test.cpp index 8dfbec9c7..725d3b340 100644 --- a/tests/vault/gear/scheduler-invocation-test.cpp +++ b/tests/vault/gear/scheduler-invocation-test.cpp @@ -72,7 +72,7 @@ namespace test { CHECK (not sched.peekHead()); - sched.instruct (activity, when, dead); + sched.instruct ({activity, when, dead}); sched.feedPrioritisation(); CHECK (sched.peekHead()); @@ -93,18 +93,18 @@ namespace test { SchedulerInvocation sched; Activity one{1u,1u}; Activity two{2u,2u}; - Activity ree{3u,3u}; + Activity wee{3u,3u}; Time t{5,5}; - sched.instruct (one, t); - sched.instruct (two, t); - sched.instruct (ree, t); + sched.instruct ({one, t}); + sched.instruct ({two, t}); + sched.instruct ({wee, t}); CHECK (not sched.peekHead()); sched.feedPrioritisation(); CHECK (isSameObject (*sched.pullHead(), one)); CHECK (isSameObject (*sched.pullHead(), two)); - CHECK (isSameObject (*sched.pullHead(), ree)); + CHECK (isSameObject (*sched.pullHead(), wee)); CHECK (not sched.peekHead()); CHECK (sched.empty()); } @@ -125,13 +125,13 @@ namespace test { Activity a3{3u,3u}; Activity a4{4u,4u}; - sched.instruct (a2, Time{2,0}); - sched.instruct (a4, Time{4,0}); + sched.instruct ({a2, Time{2,0}}); + sched.instruct ({a4, Time{4,0}}); sched.feedPrioritisation(); CHECK (isSameObject (*sched.peekHead(), a2)); - sched.instruct (a3, Time{3,0}); - sched.instruct (a1, Time{1,0}); + sched.instruct ({a3, Time{3,0}}); + sched.instruct ({a1, Time{1,0}}); CHECK (isSameObject (*sched.peekHead(), a2)); sched.feedPrioritisation(); @@ -155,10 +155,10 @@ namespace test { Activity a3{3u,3u}; Activity a4{4u,4u}; - sched.feedPrioritisation (a1, Time{0,5}); - sched.feedPrioritisation (a2, Time{0,5}); - sched.feedPrioritisation (a3, Time{0,5}); - sched.feedPrioritisation (a4, Time{0,4}); + sched.feedPrioritisation ({a1, Time{0,5}}); + sched.feedPrioritisation ({a2, Time{0,5}}); + sched.feedPrioritisation ({a3, Time{0,5}}); + sched.feedPrioritisation ({a4, Time{0,4}}); CHECK (isSameObject (*sched.pullHead(), a4)); CHECK (isSameObject (*sched.pullHead(), a3)); CHECK (isSameObject (*sched.pullHead(), a1)); @@ -177,7 +177,7 @@ namespace test { SchedulerInvocation sched; Activity a1{1u,1u}; - sched.feedPrioritisation (a1, Time{0,5}); + sched.feedPrioritisation ({a1, Time{0,5}}); CHECK (isSameObject (*sched.peekHead(), a1)); CHECK ( sched.isDue (Time{0,10})); CHECK ( sched.isDue (Time{0,5})); @@ -207,7 +207,7 @@ namespace test { SchedulerInvocation sched; Activity act; - sched.feedPrioritisation (act, Time{2,0}, Time{3,0}); + sched.feedPrioritisation ({act, Time{2,0}, Time{3,0}}); CHECK (Time(2,0) == sched.headTime()); CHECK ( sched.isDue (Time{2,0})); CHECK (not sched.isMissed (Time{2,0})); @@ -218,7 +218,7 @@ namespace test { CHECK (not sched.isOutdated (Time{3,0})); CHECK ( sched.isOutdated (Time{4,0})); - sched.feedPrioritisation (act, Time{1,0}, Time{3,0}, ManifestationID{5}); + sched.feedPrioritisation ({act, Time{1,0}, Time{3,0}, ManifestationID{5}}); CHECK (Time(1,0) == sched.headTime()); CHECK ( sched.isOutdated (Time{1,0})); CHECK (not sched.isMissed (Time{1,0})); @@ -243,8 +243,8 @@ namespace test { CHECK (not sched.isMissed (Time{1,0})); CHECK ( sched.isDue (Time{1,0})); - sched.feedPrioritisation (act, Time{0,0}, Time{2,0}, ManifestationID{23}, true); - CHECK (Time(0,0) == sched.headTime()); // ^^^^ marked as compulsory + sched.feedPrioritisation ({act, Time{0,0}, Time{2,0}, ManifestationID{23}, true}); + CHECK (Time(0,0) == sched.headTime()); // ^^^^ marked as compulsory CHECK (not sched.isMissed (Time{1,0})); CHECK (not sched.isOutdated (Time{1,0})); CHECK (not sched.isOutOfTime(Time{2,0})); // still OK /at/ deadline diff --git a/tests/vault/gear/scheduler-service-test.cpp b/tests/vault/gear/scheduler-service-test.cpp index e0ddde342..e16cb1589 100644 --- a/tests/vault/gear/scheduler-service-test.cpp +++ b/tests/vault/gear/scheduler-service-test.cpp @@ -113,9 +113,7 @@ namespace test { CHECK (isnil (scheduler)); Activity dummy{Activity::FEED}; - auto postIt = [&] { auto& schedCtx = Scheduler::ExecutionCtx::from(scheduler); - schedCtx.post (RealClock::now()+t200us, &dummy, schedCtx); - }; + auto postIt = [&] { scheduler.postChain (ActivationEvent{dummy, RealClock::now()+t200us}); }; scheduler.ignite(); CHECK (isnil (scheduler)); // no start without any post() @@ -167,9 +165,8 @@ namespace test { auto createLoad = [&](Offset start, uint cnt) { // use internal API (this test is declared as friend) - auto& schedCtx = Scheduler::ExecutionCtx::from(scheduler); - for (uint i=0; i + + + + + +

+ hier spielt eine Deadline nur die Rolle einer zu prüfenden Bedingung +

+ +
+
+ + + + +

+ diese enthalten eine Activity, aber auch ein Zeitfenster sowie weitere Kontext-Parameter (ManifestationID, Flags wie isCompulsory) +

+ +
+
+ + + + + + @@ -82678,9 +82705,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - + + + @@ -82703,37 +82730,62 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...denn sonst würde in jedem Fall der Zugriff komplizierter; zwar handelt es sich um ein Implementierungs-Detail, aber im ScheudlerService selber darf es durchaus Verkopplung auf die Implementierung geben

- -
+
- - - +

Wir sind hier in einem »inline«-Universum, und die wenigen Verwendungen machen entweder direkt die bool-conversion oder speichern die Datenwerte irgendwo auf den Stack. Da is nix „schwergewichtig“

- -
+
- - + + + + + + + + + + + + + + + + + + + + + + +

+ neuer Haupt-Eingang: postChain(ActivationEvent) +

+ +
+ + + + + +
@@ -84349,13 +84401,11 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - - +

das heißt, das ist latent ein Design-Problem — welches ich derzeit nicht lösen kann, da mir der Gesamtüberblick noch fehlt, und ich genau deshalb dieses offene Design gewählt habe @@ -84366,9 +84416,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

kann es auch gar nicht, denn die Activity selber kennt i.A. keine Deadline @@ -84376,7 +84424,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + @@ -84384,7 +84433,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -84392,11 +84441,40 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + +

+ Umbau: Dispatch-mit-Kontext +

+ +
- + - + + + + + + +

+ der Queue-Entry wurde zum ActivationEvent als zentrale Austausch-Einheit +

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