diff --git a/src/lib/iter-adapter.hpp b/src/lib/iter-adapter.hpp index 581613611..069d57fcd 100644 --- a/src/lib/iter-adapter.hpp +++ b/src/lib/iter-adapter.hpp @@ -135,7 +135,7 @@ namespace lib { * - it is not just a disguised pointer (meaning, it's more expensive) * - it checks validity on every operation and may throw * - it has a distinct back-link to the source container - * - the source container needs to provide hasNext() and iterNext() free functions. + * - the source container needs to support `checkPoint()` and `iterNext()` free functions. * - we may need friendship to implement those extension points on the container * - the end-of-iteration can be detected by bool check * @note it is possible to "hide" a smart-ptr within the CON template parameter. @@ -303,7 +303,7 @@ namespace lib { inline bool operator== (IterAdapter const& il, IterAdapter const& ir) { return il.pos_ == ir.pos_; } template - inline bool operator!= (IterAdapter const& il, IterAdapter const& ir) { return !(il == ir); } + inline bool operator!= (IterAdapter const& il, IterAdapter const& ir) { return not (il == ir); } diff --git a/src/vault/gear/activity.hpp b/src/vault/gear/activity.hpp index dd12beb44..1f8ad14a3 100644 --- a/src/vault/gear/activity.hpp +++ b/src/vault/gear/activity.hpp @@ -181,7 +181,7 @@ namespace gear { explicit - Activity (Verb verb) + Activity (Verb verb) noexcept : verb_{verb} , next{nullptr} { @@ -193,7 +193,7 @@ namespace gear { /* ==== special case initialisation ==== */ - Activity (size_t o1, size_t o2) + Activity (size_t o1, size_t o2) noexcept : Activity{FEED} { data_.feed.one = o1; @@ -201,42 +201,42 @@ namespace gear { } explicit - Activity (Activity* target) + Activity (Activity* target) noexcept : Activity{NOTIFY} { data_.notification.target = target; } explicit - Activity (int expectNotifications, Time deadline = Time::NEVER) + Activity (int expectNotifications, Time deadline = Time::NEVER) noexcept : Activity{GATE} { data_.condition.rest = expectNotifications; data_.condition.dead = deadline; } - Activity (Time when, Activity* followUp) + Activity (Time when, Activity* followUp) noexcept : verb_{POST} , next{followUp} { data_.timeWindow = {when,when}; } - Activity (Time start, Time after, Activity* followUp) + Activity (Time start, Time after, Activity* followUp) noexcept : verb_{POST} , next{followUp} { data_.timeWindow = {start,after}; } - Activity() + Activity() noexcept : Activity{TICK} { } private: void - setDefaultArg (Verb verb) + setDefaultArg (Verb verb) noexcept { data_ = {0,0}; switch (verb) { diff --git a/src/vault/gear/block-flow.hpp b/src/vault/gear/block-flow.hpp index 4fabda044..7cfd17229 100644 --- a/src/vault/gear/block-flow.hpp +++ b/src/vault/gear/block-flow.hpp @@ -34,7 +34,7 @@ ** activities, necessitating timely clean-up of obsolete descriptors. ** Used memory should be recycled, calling for an arrangement of ** pooled allocation tiles, extending the underlying block - ** allocation on increased throughput. + ** allocation on increased throughput. ** ** @note currently this rather marks the intended memory management pattern, ** while the actual allocations are still performed on the heap. @@ -60,6 +60,7 @@ //#include "lib/util.hpp" //#include +#include namespace vault{ @@ -75,30 +76,66 @@ namespace gear { const size_t INITIAL_FRAMES = 50; const size_t INITIAL_ALLOC = 1 + (INITIAL_FRAMES * ACTIVITIES_PER_FRAME) / EPOCH_SIZ; + /** raw allocator to provide a sequence of Extents to place Activity records */ using Allocator = mem::ExtentFamily; } + /** - * + * Allocation Extent holding _scheduler Activities_ to be performed altogether + * before a common _deadline._ Other than the underlying raw Extent, the Epoch + * maintains a deadline time and keeps track of storage slots already claimed. + * This is achieved by using the Activity record in the first slot as a GATE term + * to maintain those administrative information. + * @remark rationale is to discard the Extent as a whole, once the deadline passed. */ class Epoch : public Allocator::Extent { - + /// @warning will be faked, not constructed Epoch() = delete; public: + /** + * specifically rigged GATE Activity, + * used for managing Epoch metadata + * - the Condition::rest tracks pending async IO operations + * - the Condition::deadline is the nominal deadline of this Epoch + * - the field `next` points to the next free allocation Slot to use + */ struct EpochGate : Activity { + /** @note initially by default there is... + * - effectively no deadline + * - no IO operations pending (i.e. we can just discard the Epoch) + * - the `next` usable Slot is the last Storage slot, and will be + * decremented until there is only one slot left (EpochGate itself) + * @warning EpochGate is assumed to sit in the Epoch's first slot + */ EpochGate() : Activity{int(0), Time::ANYTIME} { - UNIMPLEMENTED ("initialise allocation usage marker to zero"); + // initialise allocation usage marker: start at last usable slot + next = this + (Epoch::SIZ() - 1); + ENSURE (next != this); } // default copyable + + bool + hasFreeSlot() + { // see C++ § 5.9 : comparison of pointers within same array + return next > this; + } + + Activity* + claimNextSlot() + { + REQUIRE (hasFreeSlot()); + return next--; + } }; static Epoch& @@ -116,8 +153,12 @@ namespace gear { } }; + + /** - * Basic (abstracted) view of... + * Allocation scheme for the Scheduler, based on Epoch(s). + * Scheduling entails to provide a chain of Activity definitions, + * which will then »flow« through the priority queue until invocation. * * @see SchedulerCommutator * @see BlockFlow_test @@ -132,10 +173,51 @@ namespace gear { : alloc_{INITIAL_ALLOC} { } - Activity& - createActivity (Activity::Verb verb, Time deadline) + /** + * Local handle to allow allocating a collection of Activities, + * all sharing a common deadline. Internally, these records are + * maintained in fixed-sized _extents_ and thus allocations may + * _overflow_ — leading to allocation of further extents. However, + * this extension is handled transparently by the embedded iterator. + */ + class AllocatorHandle { - UNIMPLEMENTED ("place new allocation"); + Allocator::iterator extent; + + public: + template + Activity& + create (ARGS&& ...args) + { + return *new(claimSlot()) Activity {std::forward (args)...}; + } + + private: + Epoch& + currEpoch() + { + REQUIRE (bool(extent)); + return static_cast (extent->access()); ////////////////////////////OOO is it possible to adapt the iterator, so that dereferentiation directly yields the Extent? + } + + void* + claimSlot() ///< EX_SANE + { + if (currEpoch().gate().hasFreeSlot()) + { + return currEpoch().gate().claimNextSlot(); + } + else // Epoch overflow + { // use following Epoch; possibly allocate + UNIMPLEMENTED ("advance to next epoch"); + } + } + }; + + AllocatorHandle + until (Time deadline) + { + UNIMPLEMENTED ("search through existing Epochs to locate the latest one to support given deadline"); } void diff --git a/src/vault/mem/extent-family.hpp b/src/vault/mem/extent-family.hpp index d7e156327..7746e0f0d 100644 --- a/src/vault/mem/extent-family.hpp +++ b/src/vault/mem/extent-family.hpp @@ -46,7 +46,8 @@ #include "vault/common.hpp" //#include "lib/symbol.hpp" -#include "lib/nocopy.hpp" +#include "lib/iter-adapter.hpp" +#include "lib/nocopy.hpp" //#include "lib/util.hpp" //#include @@ -76,6 +77,7 @@ namespace mem { : std::array { using Payload = T; + using SIZ = std::integral_constant; }; private: @@ -101,6 +103,7 @@ namespace mem { } }; using Extents = std::vector; + using RawIter = typename Extents::iterator; Extents extents_; @@ -119,6 +122,23 @@ namespace mem { { extents_.reserve (expectedMaxExtents); } + + /** allow transparent iteration of Extents, expanding storage on demand */ + using iterator = lib::IterAdapter; ////////////////////OOO consider to use a Extent* instead of the RawIter?? + + /* == Iteration control API (used by IterAdapter via ADL) == */ + + friend bool + checkPoint (ExtentFamily* exFam, RawIter& pos) + { + UNIMPLEMENTED ("ExtentFamily iteration control: check and adapt position"); + } + + friend void + iterNext (ExtentFamily* exFam, RawIter& pos) + { + UNIMPLEMENTED ("ExtentFamily iteration control: access next Extent, possibly expand allocation"); + } }; diff --git a/tests/library/iter-adapter-test.cpp b/tests/library/iter-adapter-test.cpp index 89b20101e..c29bbb0a0 100644 --- a/tests/library/iter-adapter-test.cpp +++ b/tests/library/iter-adapter-test.cpp @@ -144,9 +144,9 @@ namespace test{ template friend void iterNext (const TestContainer*, ITER& pos) - { - ++pos; - } + { + ++pos; + } /** Implementation of Iteration-logic: detect iteration end. * @note the problem here is that this implementation chooses to use @@ -161,15 +161,15 @@ namespace test{ template friend bool checkPoint (const TestContainer* src, ITER& pos) - { - REQUIRE (src); - if ((pos != ITER()) && (pos != src->numberz_.end())) - return true; - else - { - pos = ITER(); - return false; - } } + { + REQUIRE (src); + if ((pos != ITER()) && (pos != src->numberz_.end())) + return true; + else + { + pos = ITER(); + return false; + } } }; } // (END) impl test dummy container diff --git a/tests/vault/gear/block-flow-test.cpp b/tests/vault/gear/block-flow-test.cpp index 4354925bf..f73ac460e 100644 --- a/tests/vault/gear/block-flow-test.cpp +++ b/tests/vault/gear/block-flow-test.cpp @@ -76,7 +76,7 @@ namespace test { { BlockFlow bFlow; Time deadline = randTime(); - Activity tick = bFlow.createActivity(Activity::TICK, deadline); + Activity tick = bFlow.until(deadline).create(); ///////////////////////////////////////////////////////////////////////////////OOO diagnostic function to check allocation bFlow.discardBefore (deadline + Time{0,5}); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 63e58c4a9..df3f23932 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -78646,7 +78646,15 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + + + + + + @@ -78741,6 +78749,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200

+
@@ -78879,11 +78888,38 @@ Date:   Thu Apr 20 18:53:17 2023 +0200

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

+ ...da wir den Platz im EpochGuard bereits anderweitig komplett ausschöpfen, muß die ExtentFamily auch beim Navigieren über die Sequenz der Epochen hinweg helfen. Und im Besonderen eine Navigation über das Ende hinaus muß transparent eine Erweiterung der Allokation  bewirken. Habe mich entschlossen, die Limitierung als Ausnahme/Exteption zu definieren, so daß im Regelfall die ExtentFamily einfach immer weiter wachsen soll +

+ +
+ +
+ + +
@@ -78907,7 +78943,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -78926,10 +78962,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - + + @@ -78948,6 +78984,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + @@ -78968,28 +79007,102 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + - + - - + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + +

+ ...denn speziell das Count-down-Feld ist gradezu der springende Punkt beim EpochGate (damit lösen wir das Problem, daß IO-Operationen hängenbleiben können) +

+ +
+ + +
+ + + + + + + + + + + + + + + + + +
@@ -78997,6 +79110,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + +
@@ -79004,17 +79124,17 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - +

- zwar wird ein POST geplant, + ein POST könnte geplant werden,

aber die angehängten Activities werden @@ -79026,6 +79146,69 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + +

+ Ich gehe davon aus, daß „dieser ganze Scheduler“ so dimensioniert ist und sich selber derart einregelt, daß er mit dem Flow der Berechnungen klarkommt. Aufgrund der dynamischen und offenen Natur dieser Konstruktion kann ich jedoch keine Garantien geben, daß diese Steuerung stets funktioniert. Die Activity-Language schafft ja grade die Möglichkeit, gegenwärtig noch gar nicht vorstellbare Steuerungs-Mechanismen später noch realisieren zu können, lediglich durch eine veränderte Parametrisierung. Aber zumindest ein Szenario des Scheiterns kann aus aktueller Sicht bereits konstruiert werden: und zwar wenn durch eine Überlastung des IO-Subsystems ältere Epochen noch nicht freigegeben werden können, da an ihnen noch Buffer für schwebende IO-Vorgänge hängen. Ein laufend fortschreitender BlockFlow würde dann sukzessive den verbleibenden Arbeitsspeicher belegen und blockieren. +

+ +
+
+ + + + + + +

+ Ein im BlockFlow aktiv verwaltetes Limit für den Allokations-Pool stellt daher eine Sicherheitsbarriere dar, damit das System nicht insgesamt in einen degenerierten und unkontrollierbaren Betriebszustand gleitet (sog. „trashing“). Dem Zufolge sollte ein Auslösen dieser Sicherung nicht in Reparatur-Versuche münden, sondern schnellstmöglich zu einem Scheitern des Playback/Rendervorganges führen +

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

+ Es ist klar, wenn eine Epoche überläuft, aber in dem Moment ist noch nicht klar, welcher Prozentsatz des Platztes nun noch unterzubringen ist. Umgekehrt ist nicht klar, wann eine Epoche fertig besetzt ist; Hilfsweise könnte man beim Aufräumen prüfen, was aber erst deutlich später passiert — in beide Richtungen ist also die Rückkopplung verzögert und ungenau und kann daher zu Regelschwingungen führen. +

+ +
+ + + + + + + + + +
@@ -79091,6 +79274,22 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + +